@geraldmaron/construct 1.0.3 → 1.0.5
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 +51 -42
- package/agents/prompts/cx-ai-engineer.md +6 -26
- package/agents/prompts/cx-architect.md +1 -0
- package/agents/prompts/cx-business-strategist.md +2 -0
- package/agents/prompts/cx-data-analyst.md +6 -26
- package/agents/prompts/cx-docs-keeper.md +1 -31
- package/agents/prompts/cx-explorer.md +1 -0
- package/agents/prompts/cx-orchestrator.md +40 -112
- package/agents/prompts/cx-platform-engineer.md +2 -22
- package/agents/prompts/cx-product-manager.md +2 -1
- package/agents/prompts/cx-qa.md +0 -20
- package/agents/prompts/cx-rd-lead.md +2 -0
- package/agents/prompts/cx-researcher.md +77 -31
- package/agents/prompts/cx-security.md +11 -49
- package/agents/prompts/cx-sre.md +9 -43
- package/agents/prompts/cx-ux-researcher.md +1 -0
- package/agents/role-manifests.json +4 -4
- package/bin/construct +279 -4
- package/commands/understand/research.md +5 -3
- package/db/schema/004_recommendations.sql +46 -0
- package/db/schema/005_strategy.sql +21 -0
- package/lib/auto-docs.mjs +3 -4
- package/lib/beads-automation.mjs +16 -7
- package/lib/cli-commands.mjs +52 -2
- package/lib/comment-lint.mjs +7 -1
- package/lib/config/schema.mjs +3 -0
- package/lib/embed/conflict-detection.mjs +26 -9
- package/lib/embed/customer-profiles.mjs +37 -17
- package/lib/embed/daemon.mjs +10 -8
- package/lib/embed/recommendation-store.mjs +213 -14
- package/lib/embed/workspaces.mjs +53 -18
- package/lib/flavors/loader.mjs +136 -0
- package/lib/gates-audit.mjs +3 -3
- package/lib/health-check.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +22 -3
- package/lib/hooks/pre-compact.mjs +3 -0
- package/lib/hooks/pre-push-gate.mjs +14 -1
- package/lib/hooks/read-tracker.mjs +10 -101
- package/lib/hooks/session-optimize.mjs +3 -2
- package/lib/hooks/session-reflect.mjs +68 -0
- package/lib/host-capabilities.mjs +90 -1
- package/lib/init-unified.mjs +25 -2
- package/lib/init-update.mjs +246 -131
- package/lib/intake/classify.mjs +61 -183
- package/lib/intake/prepare.mjs +7 -0
- package/lib/intake/tables/creative.mjs +94 -0
- package/lib/intake/tables/operations.mjs +85 -0
- package/lib/intake/tables/research.mjs +85 -0
- package/lib/intake/tables/rnd.mjs +175 -0
- package/lib/intent-classifier.mjs +1 -0
- package/lib/knowledge/layout.mjs +10 -0
- package/lib/knowledge/research-store.mjs +109 -0
- package/lib/mcp/tools/telemetry.mjs +30 -78
- package/lib/model-router.mjs +61 -1
- package/lib/observation-store.mjs +19 -0
- package/lib/ollama-manager.mjs +1 -1
- package/lib/opencode-telemetry.mjs +4 -5
- package/lib/orchestration-policy.mjs +9 -0
- package/lib/outcomes/aggregate.mjs +104 -0
- package/lib/outcomes/record.mjs +115 -0
- package/lib/parity.mjs +121 -21
- package/lib/profiles/lifecycle.mjs +388 -0
- package/lib/profiles/loader.mjs +123 -0
- package/lib/profiles/validate-custom.mjs +114 -0
- package/lib/prompt-composer.js +106 -29
- package/lib/read-tracker-store.mjs +149 -0
- package/lib/reflect/extractor.mjs +193 -0
- package/lib/reflect.mjs +89 -2
- package/lib/sandbox.mjs +102 -0
- package/lib/server/index.mjs +76 -0
- package/lib/server/telemetry-login.mjs +17 -25
- package/lib/service-manager.mjs +30 -22
- package/lib/services/local-postgres.mjs +15 -0
- package/lib/services/telemetry-backend.mjs +1 -2
- package/lib/setup.mjs +8 -43
- package/lib/status.mjs +51 -5
- package/lib/storage/backend.mjs +12 -2
- package/lib/strategy-store.mjs +371 -0
- package/lib/telemetry/backends/local.mjs +6 -4
- package/lib/telemetry/client.mjs +185 -0
- package/lib/telemetry/ingest.mjs +13 -5
- package/lib/telemetry/team-rollup.mjs +9 -2
- package/lib/worker/trace.mjs +17 -27
- package/package.json +10 -2
- package/personas/construct.md +20 -20
- package/platforms/claude/settings.template.json +13 -0
- package/rules/common/research.md +44 -12
- package/scripts/sync-agents.mjs +11 -0
- package/skills/docs/backlog-proposal-workflow.md +2 -2
- package/skills/docs/customer-profile-workflow.md +1 -1
- package/skills/docs/evidence-ingest-workflow.md +5 -5
- package/skills/docs/prfaq-workflow.md +1 -1
- package/skills/docs/product-intelligence-review.md +1 -1
- package/skills/docs/product-intelligence-workflow.md +3 -3
- package/skills/docs/product-signal-workflow.md +48 -18
- package/skills/docs/research-workflow.md +26 -14
- package/skills/docs/strategy-workflow.md +36 -0
- package/skills/roles/architect.ai-systems.md +4 -2
- package/skills/roles/architect.data.md +4 -2
- package/skills/roles/architect.enterprise.md +4 -2
- package/skills/roles/architect.integration.md +4 -2
- package/skills/roles/architect.md +7 -5
- package/skills/roles/architect.platform.md +4 -2
- package/skills/roles/data-analyst.experiment.md +4 -2
- package/skills/roles/data-analyst.md +9 -7
- package/skills/roles/data-analyst.product-intelligence.md +5 -3
- package/skills/roles/data-analyst.product.md +4 -2
- package/skills/roles/data-analyst.telemetry.md +4 -2
- package/skills/roles/data-engineer.pipeline.md +4 -2
- package/skills/roles/data-engineer.vector-retrieval.md +4 -2
- package/skills/roles/data-engineer.warehouse.md +4 -2
- package/skills/roles/debugger.md +7 -5
- package/skills/roles/designer.accessibility.md +4 -2
- package/skills/roles/designer.md +10 -8
- package/skills/roles/engineer.ai.md +4 -2
- package/skills/roles/engineer.data.md +5 -3
- package/skills/roles/engineer.md +14 -12
- package/skills/roles/engineer.platform.md +5 -3
- package/skills/roles/operator.docs.md +6 -4
- package/skills/roles/operator.md +6 -4
- package/skills/roles/operator.release.md +4 -2
- package/skills/roles/operator.sre.md +5 -3
- package/skills/roles/orchestrator.md +5 -3
- package/skills/roles/product-manager.ai-product.md +4 -2
- package/skills/roles/product-manager.business-strategy.md +4 -2
- package/skills/roles/product-manager.enterprise.md +4 -2
- package/skills/roles/product-manager.growth.md +4 -2
- package/skills/roles/product-manager.md +6 -4
- package/skills/roles/product-manager.platform.md +4 -2
- package/skills/roles/product-manager.product.md +4 -2
- package/skills/roles/qa.ai-eval.md +4 -2
- package/skills/roles/qa.api-contract.md +4 -2
- package/skills/roles/qa.data-pipeline.md +4 -2
- package/skills/roles/qa.md +7 -5
- package/skills/roles/qa.test-automation.md +5 -3
- package/skills/roles/qa.web-ui.md +4 -2
- package/skills/roles/researcher.explorer.md +4 -2
- package/skills/roles/researcher.md +35 -20
- package/skills/roles/researcher.ux.md +4 -2
- package/skills/roles/reviewer.devil-advocate.md +4 -2
- package/skills/roles/reviewer.evaluator.md +4 -2
- package/skills/roles/reviewer.md +14 -12
- package/skills/roles/reviewer.trace.md +4 -2
- package/skills/roles/security.ai.md +4 -2
- package/skills/roles/security.appsec.md +4 -2
- package/skills/roles/security.cloud.md +4 -2
- package/skills/roles/security.legal-compliance.md +4 -2
- package/skills/roles/security.md +7 -5
- package/skills/roles/security.privacy.md +4 -2
- package/skills/roles/security.supply-chain.md +4 -2
- package/skills/routing.md +8 -1
- package/templates/docs/persona-artifact.md +36 -0
- package/templates/docs/research-brief.md +63 -9
- package/templates/docs/research-finding.md +26 -0
- package/templates/docs/skill-artifact.md +27 -0
- package/templates/docs/strategy.md +36 -0
- package/templates/homebrew/construct.rb +6 -6
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/telemetry/client.mjs — shared local-first telemetry adapter.
|
|
3
|
+
*
|
|
4
|
+
* Local JSONL trace capture is the default. Remote export is opt-in through
|
|
5
|
+
* CONSTRUCT_TRACE_BACKEND and never throws into callers.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { randomUUID } from 'node:crypto';
|
|
11
|
+
|
|
12
|
+
import { createIngestClient } from './ingest.mjs';
|
|
13
|
+
|
|
14
|
+
const DEFAULT_MAX_BATCH = 50;
|
|
15
|
+
|
|
16
|
+
export const TRACE_BACKENDS = new Set(['local', 'langfuse', 'http', 'otel', 'none']);
|
|
17
|
+
|
|
18
|
+
function cleanUrl(value = '') {
|
|
19
|
+
return String(value || '').replace(/\/$/, '');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function inferRemoteBackend(env) {
|
|
23
|
+
const url = cleanUrl(env.CONSTRUCT_TELEMETRY_URL);
|
|
24
|
+
const hasKeys = Boolean(env.CONSTRUCT_TELEMETRY_PUBLIC_KEY && env.CONSTRUCT_TELEMETRY_SECRET_KEY);
|
|
25
|
+
if (!url) return 'local';
|
|
26
|
+
return hasKeys ? 'langfuse' : 'http';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function resolveTraceBackend(env = process.env) {
|
|
30
|
+
const raw = String(env.CONSTRUCT_TRACE_BACKEND || '').trim().toLowerCase();
|
|
31
|
+
if (!raw) return inferRemoteBackend(env);
|
|
32
|
+
if (raw === 'remote' || raw === 'telemetry') return inferRemoteBackend(env);
|
|
33
|
+
if (raw === 'off') return 'none';
|
|
34
|
+
return TRACE_BACKENDS.has(raw) ? raw : 'local';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function telemetryProviderLabel(env = process.env) {
|
|
38
|
+
return env.CONSTRUCT_TELEMETRY_PROVIDER || resolveTraceBackend(env);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolveProjectRoot({ rootDir, env = process.env } = {}) {
|
|
42
|
+
return rootDir || env.CX_TOOLKIT_DIR || env.PWD || process.cwd();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function localTraceEnabled(env = process.env) {
|
|
46
|
+
return env.CONSTRUCT_TRACE_LOCAL_ENABLED !== '0';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function traceShard() {
|
|
50
|
+
return new Date().toISOString().slice(0, 10);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function appendLocalTelemetry(type, body, { rootDir, env = process.env, onError = () => {} } = {}) {
|
|
54
|
+
if (!localTraceEnabled(env)) return;
|
|
55
|
+
const projectRoot = resolveProjectRoot({ rootDir, env });
|
|
56
|
+
const dir = join(projectRoot, '.cx', 'traces');
|
|
57
|
+
try {
|
|
58
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
59
|
+
appendFileSync(join(dir, `${traceShard()}.jsonl`), `${JSON.stringify({
|
|
60
|
+
traceId: body?.traceId || body?.id || randomUUID(),
|
|
61
|
+
spanId: body?.id || randomUUID(),
|
|
62
|
+
eventType: `telemetry.${type}`,
|
|
63
|
+
telemetryType: type,
|
|
64
|
+
project: body?.metadata?.project || body?.project || null,
|
|
65
|
+
role: body?.metadata?.agent || body?.metadata?.agentName || body?.name || null,
|
|
66
|
+
metadata: body?.metadata || {},
|
|
67
|
+
input: body?.input,
|
|
68
|
+
output: body?.output,
|
|
69
|
+
createdAt: body?.timestamp || body?.startTime || new Date().toISOString(),
|
|
70
|
+
})}\n`, 'utf8');
|
|
71
|
+
} catch (err) {
|
|
72
|
+
onError(err);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildOtlpPayload(batch) {
|
|
77
|
+
return {
|
|
78
|
+
resourceSpans: [{
|
|
79
|
+
resource: {
|
|
80
|
+
attributes: [
|
|
81
|
+
{ key: 'service.name', value: { stringValue: 'construct' } },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
scopeSpans: [{
|
|
85
|
+
scope: { name: 'construct.telemetry' },
|
|
86
|
+
spans: batch.map((item) => {
|
|
87
|
+
const body = item.body || {};
|
|
88
|
+
const startedAt = Date.parse(body.startTime || body.timestamp || item.timestamp || new Date().toISOString());
|
|
89
|
+
const endedAt = Date.parse(body.endTime || body.startTime || body.timestamp || item.timestamp || new Date().toISOString());
|
|
90
|
+
return {
|
|
91
|
+
traceId: String(body.traceId || body.id || item.id).replace(/[^a-fA-F0-9]/g, '').padEnd(32, '0').slice(0, 32),
|
|
92
|
+
spanId: String(body.id || item.id).replace(/[^a-fA-F0-9]/g, '').padEnd(16, '0').slice(0, 16),
|
|
93
|
+
name: body.name || item.type,
|
|
94
|
+
kind: 1,
|
|
95
|
+
startTimeUnixNano: String(Math.max(0, startedAt) * 1_000_000),
|
|
96
|
+
endTimeUnixNano: String(Math.max(0, endedAt) * 1_000_000),
|
|
97
|
+
attributes: [
|
|
98
|
+
{ key: 'construct.telemetry.type', value: { stringValue: item.type } },
|
|
99
|
+
...(body.model ? [{ key: 'llm.model_name', value: { stringValue: String(body.model) } }] : []),
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}),
|
|
103
|
+
}],
|
|
104
|
+
}],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function createRemoteClient({ backend, env, fetchImpl, onError }) {
|
|
109
|
+
if (backend === 'local' || backend === 'none') return null;
|
|
110
|
+
if (backend === 'otel') {
|
|
111
|
+
const endpoint = cleanUrl(env.CONSTRUCT_OTEL_EXPORTER_OTLP_ENDPOINT);
|
|
112
|
+
return createIngestClient({
|
|
113
|
+
baseUrl: endpoint,
|
|
114
|
+
endpointPath: endpoint.endsWith('/v1/traces') ? '' : '/v1/traces',
|
|
115
|
+
authMode: 'none',
|
|
116
|
+
payloadBuilder: buildOtlpPayload,
|
|
117
|
+
fetchImpl,
|
|
118
|
+
onError,
|
|
119
|
+
maxBatch: DEFAULT_MAX_BATCH,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if (backend === 'http') {
|
|
123
|
+
return createIngestClient({
|
|
124
|
+
baseUrl: cleanUrl(env.CONSTRUCT_TELEMETRY_URL),
|
|
125
|
+
endpointPath: '/ingest',
|
|
126
|
+
authMode: 'none',
|
|
127
|
+
fetchImpl,
|
|
128
|
+
onError,
|
|
129
|
+
maxBatch: DEFAULT_MAX_BATCH,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return createIngestClient({
|
|
133
|
+
baseUrl: cleanUrl(env.CONSTRUCT_TELEMETRY_URL),
|
|
134
|
+
publicKey: env.CONSTRUCT_TELEMETRY_PUBLIC_KEY,
|
|
135
|
+
secretKey: env.CONSTRUCT_TELEMETRY_SECRET_KEY,
|
|
136
|
+
fetchImpl,
|
|
137
|
+
onError,
|
|
138
|
+
maxBatch: DEFAULT_MAX_BATCH,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function createTelemetryClient({
|
|
143
|
+
env = process.env,
|
|
144
|
+
rootDir,
|
|
145
|
+
fetchImpl = globalThis.fetch,
|
|
146
|
+
onError = () => {},
|
|
147
|
+
localWrites = true,
|
|
148
|
+
} = {}) {
|
|
149
|
+
const backend = resolveTraceBackend(env);
|
|
150
|
+
const remote = createRemoteClient({ backend, env, fetchImpl, onError });
|
|
151
|
+
const localAvailable = localWrites && localTraceEnabled(env);
|
|
152
|
+
const remoteAvailable = Boolean(remote?.available);
|
|
153
|
+
|
|
154
|
+
function emit(type, body) {
|
|
155
|
+
if (!body) return;
|
|
156
|
+
if (localWrites) appendLocalTelemetry(type, body, { rootDir, env, onError });
|
|
157
|
+
if (!remoteAvailable) return;
|
|
158
|
+
try {
|
|
159
|
+
remote[type]?.(body);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
onError(err);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
backend,
|
|
167
|
+
provider: telemetryProviderLabel(env),
|
|
168
|
+
available: localAvailable || remoteAvailable,
|
|
169
|
+
localAvailable,
|
|
170
|
+
remoteAvailable,
|
|
171
|
+
remoteStatus: remoteAvailable ? 'configured' : backend === 'local' || backend === 'none' ? backend : 'unconfigured',
|
|
172
|
+
trace: (body) => emit('trace', body),
|
|
173
|
+
traceUpdate: (body) => emit('traceUpdate', body),
|
|
174
|
+
generation: (body) => emit('generation', body),
|
|
175
|
+
generationUpdate: (body) => emit('generationUpdate', body),
|
|
176
|
+
span: (body) => emit('span', body),
|
|
177
|
+
spanUpdate: (body) => emit('spanUpdate', body),
|
|
178
|
+
event: (body) => emit('event', body),
|
|
179
|
+
score: (body) => emit('score', body),
|
|
180
|
+
async flush() {
|
|
181
|
+
if (remote?.flush) await remote.flush();
|
|
182
|
+
},
|
|
183
|
+
queueSize: () => remote?.queueSize?.() ?? 0,
|
|
184
|
+
};
|
|
185
|
+
}
|
package/lib/telemetry/ingest.mjs
CHANGED
|
@@ -35,17 +35,21 @@ export function createIngestClient({
|
|
|
35
35
|
).replace(/\/$/, ''),
|
|
36
36
|
publicKey = process.env.CONSTRUCT_TELEMETRY_PUBLIC_KEY,
|
|
37
37
|
secretKey = process.env.CONSTRUCT_TELEMETRY_SECRET_KEY,
|
|
38
|
+
endpointPath = '/api/public/ingestion',
|
|
39
|
+
authMode = 'basic',
|
|
40
|
+
payloadBuilder = (batch) => ({ batch }),
|
|
38
41
|
flushIntervalMs = DEFAULT_FLUSH_INTERVAL_MS,
|
|
39
42
|
maxBatch = DEFAULT_MAX_BATCH,
|
|
40
43
|
onError = () => {},
|
|
41
44
|
fetchImpl = globalThis.fetch,
|
|
42
45
|
} = {}) {
|
|
43
|
-
const
|
|
46
|
+
const requiresBasicAuth = authMode === 'basic';
|
|
47
|
+
const available = Boolean(baseUrl && fetchImpl && (!requiresBasicAuth || (publicKey && secretKey)));
|
|
44
48
|
const queue = [];
|
|
45
49
|
let flushTimer = null;
|
|
46
50
|
let inflight = Promise.resolve();
|
|
47
51
|
|
|
48
|
-
const authHeader = available
|
|
52
|
+
const authHeader = available && requiresBasicAuth
|
|
49
53
|
? `Basic ${Buffer.from(`${publicKey}:${secretKey}`).toString('base64')}`
|
|
50
54
|
: '';
|
|
51
55
|
|
|
@@ -61,14 +65,17 @@ export function createIngestClient({
|
|
|
61
65
|
async function flushBatch() {
|
|
62
66
|
if (!available || queue.length === 0) return;
|
|
63
67
|
const batch = queue.splice(0, maxBatch);
|
|
64
|
-
const payload = JSON.stringify(
|
|
68
|
+
const payload = JSON.stringify(payloadBuilder(batch));
|
|
65
69
|
const prev = inflight;
|
|
66
70
|
inflight = (async () => {
|
|
67
71
|
try { await prev; } catch { /* ignore prior */ }
|
|
68
72
|
try {
|
|
69
|
-
const res = await fetchImpl(`${baseUrl}
|
|
73
|
+
const res = await fetchImpl(`${baseUrl}${endpointPath}`, {
|
|
70
74
|
method: 'POST',
|
|
71
|
-
headers: {
|
|
75
|
+
headers: {
|
|
76
|
+
...(authHeader ? { Authorization: authHeader } : {}),
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
},
|
|
72
79
|
body: payload,
|
|
73
80
|
});
|
|
74
81
|
if (!res.ok && res.status !== 207) {
|
|
@@ -104,6 +111,7 @@ export function createIngestClient({
|
|
|
104
111
|
span: (body) => push('span-create', body),
|
|
105
112
|
spanUpdate: (body) => push('span-update', body),
|
|
106
113
|
event: (body) => push('event-create', body),
|
|
114
|
+
score: (body) => push('score-create', body),
|
|
107
115
|
async flush() {
|
|
108
116
|
if (flushTimer) { clearTimeout(flushTimer); flushTimer = null; }
|
|
109
117
|
while (queue.length > 0) await flushBatch();
|
|
@@ -3,22 +3,29 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Groups agent traces by teamId (stamped by construct headhunt at overlay creation),
|
|
5
5
|
* computes per-team success rates, latency, handoff counts, and common blockers.
|
|
6
|
-
* Backend is selected via CONSTRUCT_TRACE_BACKEND env var (default:
|
|
6
|
+
* Backend is selected via CONSTRUCT_TRACE_BACKEND env var (default: local).
|
|
7
7
|
* Called by 'construct team review'.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import * as remoteBackend from './backends/remote.mjs';
|
|
11
|
+
import * as localBackend from './backends/local.mjs';
|
|
11
12
|
import * as noopBackend from './backends/noop.mjs';
|
|
13
|
+
import { resolveTraceBackend } from './client.mjs';
|
|
12
14
|
|
|
13
15
|
const BACKENDS = {
|
|
16
|
+
local: localBackend,
|
|
17
|
+
langfuse: remoteBackend,
|
|
18
|
+
http: remoteBackend,
|
|
19
|
+
otel: noopBackend,
|
|
14
20
|
remote: remoteBackend,
|
|
21
|
+
none: noopBackend,
|
|
15
22
|
noop: noopBackend,
|
|
16
23
|
};
|
|
17
24
|
|
|
18
25
|
const DEFAULT_WINDOW_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
19
26
|
|
|
20
27
|
function resolveBackend() {
|
|
21
|
-
const name = process.env
|
|
28
|
+
const name = resolveTraceBackend(process.env);
|
|
22
29
|
const backend = BACKENDS[name];
|
|
23
30
|
if (!backend) throw new Error(`Unknown trace backend: ${name}. Available: ${Object.keys(BACKENDS).join(', ')}`);
|
|
24
31
|
return backend;
|
package/lib/worker/trace.mjs
CHANGED
|
@@ -7,16 +7,11 @@
|
|
|
7
7
|
*
|
|
8
8
|
* 1. `.cx/traces/<YYYY-MM-DD>.jsonl` — append-only local JSONL. Always on,
|
|
9
9
|
* no credentials required. Tail live, archive, or replay.
|
|
10
|
-
* 2.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Each unique traceId triggers one trace-create plus an event-create
|
|
14
|
-
* per emitted event. Failures never throw — observability must not
|
|
10
|
+
* 2. Optional remote export — selected by CONSTRUCT_TRACE_BACKEND. Each
|
|
11
|
+
* unique traceId triggers one trace-create plus an event-create per
|
|
12
|
+
* emitted event. Failures never throw because observability must not
|
|
15
13
|
* break the caller.
|
|
16
14
|
*
|
|
17
|
-
* Reads CONSTRUCT_TELEMETRY_PUBLIC_KEY / CONSTRUCT_TELEMETRY_SECRET_KEY /
|
|
18
|
-
* CONSTRUCT_TELEMETRY_URL from env.
|
|
19
|
-
*
|
|
20
15
|
* Event types in current use:
|
|
21
16
|
* intake.received — daemon ingested a file
|
|
22
17
|
* intake.triaged — classifyRdIntake produced a triage block
|
|
@@ -35,7 +30,7 @@ import { existsSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
|
35
30
|
import { randomBytes } from 'node:crypto';
|
|
36
31
|
import path from 'node:path';
|
|
37
32
|
|
|
38
|
-
import {
|
|
33
|
+
import { createTelemetryClient } from '../telemetry/client.mjs';
|
|
39
34
|
|
|
40
35
|
const TRACE_SUBDIR = '.cx/traces';
|
|
41
36
|
|
|
@@ -73,23 +68,18 @@ export function newSpanId() {
|
|
|
73
68
|
return `span-${randomBytes(6).toString('hex')}`;
|
|
74
69
|
}
|
|
75
70
|
|
|
76
|
-
// Reused per-process. The
|
|
77
|
-
// the queue.
|
|
78
|
-
//
|
|
71
|
+
// Reused per-process. The adapter batches remote export; recreating it would
|
|
72
|
+
// lose the queue. Local JSONL is already written by emitTraceEvent, so this
|
|
73
|
+
// client disables its own local writer.
|
|
79
74
|
let telemetryClient = null;
|
|
80
75
|
const seenTraces = new Set();
|
|
81
76
|
|
|
82
|
-
function getTelemetryClient(env = process.env) {
|
|
77
|
+
function getTelemetryClient(env = process.env, rootDir) {
|
|
83
78
|
if (telemetryClient !== null) return telemetryClient;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
telemetryClient = createIngestClient({
|
|
90
|
-
baseUrl: (env.CONSTRUCT_TELEMETRY_URL ?? '').replace(/\/$/, ''),
|
|
91
|
-
publicKey: env.CONSTRUCT_TELEMETRY_PUBLIC_KEY,
|
|
92
|
-
secretKey: env.CONSTRUCT_TELEMETRY_SECRET_KEY,
|
|
79
|
+
telemetryClient = createTelemetryClient({
|
|
80
|
+
env,
|
|
81
|
+
rootDir,
|
|
82
|
+
localWrites: false,
|
|
93
83
|
});
|
|
94
84
|
return telemetryClient;
|
|
95
85
|
}
|
|
@@ -103,9 +93,9 @@ export function _resetTelemetryClient() {
|
|
|
103
93
|
}
|
|
104
94
|
|
|
105
95
|
|
|
106
|
-
function exportToRemote(event, env) {
|
|
107
|
-
const client = getTelemetryClient(env);
|
|
108
|
-
if (!client.
|
|
96
|
+
function exportToRemote(event, env, rootDir) {
|
|
97
|
+
const client = getTelemetryClient(env, rootDir);
|
|
98
|
+
if (!client.remoteAvailable) return;
|
|
109
99
|
|
|
110
100
|
// First time we see a traceId, register the trace so subsequent events
|
|
111
101
|
// attach to a known parent.
|
|
@@ -176,7 +166,7 @@ export function emitTraceEvent({
|
|
|
176
166
|
};
|
|
177
167
|
appendFileSync(path.join(dir, `${todayShard()}.jsonl`), `${JSON.stringify(event)}\n`, 'utf8');
|
|
178
168
|
|
|
179
|
-
// Fire-and-forget export to remote
|
|
180
|
-
exportToRemote(event, env);
|
|
169
|
+
// Fire-and-forget export to the configured remote adapter. Silent no-op when not configured.
|
|
170
|
+
exportToRemote(event, env, rootDir);
|
|
181
171
|
return event;
|
|
182
172
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geraldmaron/construct",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Construct — agent orchestration layer for OpenCode, Claude Code, and other coding surfaces",
|
|
6
6
|
"bin": {
|
|
@@ -42,15 +42,22 @@
|
|
|
42
42
|
"scripts": {
|
|
43
43
|
"mcp": "node lib/mcp/server.mjs",
|
|
44
44
|
"test": "node scripts/run-tests.mjs",
|
|
45
|
+
"test:functional": "node --test --test-timeout=30000 tests/functional/*.functional.test.mjs",
|
|
46
|
+
"test:unit": "node scripts/run-tests.mjs --exclude=tests/functional",
|
|
45
47
|
"docs:init": "node lib/init-docs.mjs --yes",
|
|
46
48
|
"docs:update": "node ./bin/construct docs:update",
|
|
47
49
|
"docs:site": "node ./bin/construct docs:site",
|
|
48
50
|
"docs:dev": "npm --prefix apps/docs run dev",
|
|
49
51
|
"docs:build": "npm --prefix apps/docs run build",
|
|
50
52
|
"lint:comments": "node ./bin/construct lint:comments",
|
|
53
|
+
"lint:prose": "node scripts/lint-prose.mjs",
|
|
54
|
+
"lint:profiles": "node scripts/lint-profiles.mjs",
|
|
55
|
+
"learning:status": "node scripts/learning-status.mjs",
|
|
51
56
|
"lint:templates": "node scripts/lint-commits-pr.mjs",
|
|
52
57
|
"eval:routing": "node scripts/eval/score-intent-classifier.mjs",
|
|
53
58
|
"release:check": "node ./bin/construct doctor && npm test && node ./bin/construct docs:update --check && node ./bin/construct dashboard:sync --check && node ./bin/construct lint:comments && node scripts/lint-commits-pr.mjs",
|
|
59
|
+
"release:preflight": "node scripts/pre-release-check.mjs",
|
|
60
|
+
"release:preflight:no-auth": "node scripts/pre-release-check.mjs --skip-auth",
|
|
54
61
|
"npm:publish": "npm run release:check && npm publish --access public",
|
|
55
62
|
"postinstall": "node ./bin/construct-postinstall.mjs"
|
|
56
63
|
},
|
|
@@ -61,6 +68,7 @@
|
|
|
61
68
|
"postgres": "^3.4.9"
|
|
62
69
|
},
|
|
63
70
|
"overrides": {
|
|
64
|
-
"express-rate-limit": "8.5.1"
|
|
71
|
+
"express-rate-limit": "8.5.1",
|
|
72
|
+
"protobufjs": "^8.4.2"
|
|
65
73
|
}
|
|
66
74
|
}
|
package/personas/construct.md
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
personas/construct.md
|
|
2
|
+
personas/construct.md. Construct persona prompt.
|
|
3
3
|
|
|
4
4
|
Defines the single user-facing AI interface and its session-start behavior,
|
|
5
5
|
routing rules, approval boundaries, and output contract. Loaded by sync-agents
|
|
6
6
|
and emitted to every supported platform.
|
|
7
7
|
-->
|
|
8
|
-
You are Construct
|
|
8
|
+
You are Construct. the single AI interface for everything from a quick question to a full software lifecycle. The user talks only to you; internal routing and specialist dispatch are implementation detail.
|
|
9
9
|
|
|
10
10
|
## Start of every session
|
|
11
11
|
|
|
12
|
-
Before responding, run in parallel
|
|
13
|
-
1. `project_context
|
|
14
|
-
2. `memory_search` with the basename of CWD
|
|
12
|
+
Before responding, run in parallel. do not narrate:
|
|
13
|
+
1. `project_context`. state from `.cx/context.md`
|
|
14
|
+
2. `memory_search` with the basename of CWD. prior session context and user preferences
|
|
15
15
|
3. Read `AGENTS.md`, `plan.md`, and the relevant docs for the current task when present
|
|
16
|
-
4. Check `.cx/handoffs/` for the most recent handoff
|
|
16
|
+
4. Check `.cx/handoffs/` for the most recent handoff. if another session was active, read it to understand what was in progress and what NOT to touch
|
|
17
17
|
|
|
18
18
|
Apply results silently. If memory returns preferences or past decisions, honor them without asking the user to repeat.
|
|
19
19
|
|
|
20
20
|
Honor the project operating hierarchy:
|
|
21
|
-
- Beads (`bd`) is the durable source of truth for tasks; hygiene contract
|
|
21
|
+
- Beads (`bd`) is the durable source of truth for tasks; hygiene contract. claim, close, supersede, prune. lives in `rules/common/beads-hygiene.md`. `bd ready` for unblocked work, `bd show <id>` for the active issue.
|
|
22
22
|
- `plan.md` is the human-readable implementation plan
|
|
23
23
|
- cass-memory via MCP `memory` is for cross-tool/session recall, not task tracking
|
|
24
24
|
|
|
@@ -26,12 +26,12 @@ Use the single-writer rule whenever multiple sessions are active: if two session
|
|
|
26
26
|
|
|
27
27
|
## Classify before acting
|
|
28
28
|
|
|
29
|
-
Use the code-backed orchestration policy for intent, execution track, specialist selection, escalation, and approval boundaries. Visual deliverables (wireframes, diagrams, decks) are first-class
|
|
29
|
+
Use the code-backed orchestration policy for intent, execution track, specialist selection, escalation, and approval boundaries. Visual deliverables (wireframes, diagrams, decks) are first-class. use real visual tools, not bullet prose.
|
|
30
30
|
|
|
31
31
|
Execution model:
|
|
32
|
-
- **Immediate
|
|
33
|
-
- **Focused
|
|
34
|
-
- **Orchestrated
|
|
32
|
+
- **Immediate**. answer or act directly when no hidden worker is needed.
|
|
33
|
+
- **Focused**. dispatch one bounded specialist path; return in Construct's voice.
|
|
34
|
+
- **Orchestrated**. plan → challenge → build → validate, with tracker-backed slices and explicit file ownership.
|
|
35
35
|
|
|
36
36
|
Devil's advocate is mandatory for new architectural directions, AI/agent workflow changes, security or data-integrity changes, and promoting a temporary capability to persistent.
|
|
37
37
|
|
|
@@ -39,9 +39,9 @@ Devil's advocate is mandatory for new architectural directions, AI/agent workflo
|
|
|
39
39
|
|
|
40
40
|
`routeRequest` returns three artifacts; honor all three:
|
|
41
41
|
|
|
42
|
-
1. **Gates
|
|
43
|
-
2. **Contract chain
|
|
44
|
-
3. **Specialist sequence
|
|
42
|
+
1. **Gates**. `framingChallenge`, `externalResearch`, `docAuthoring`
|
|
43
|
+
2. **Contract chain**. typed handoffs from `agents/contracts.json`. Call `agent_contract` MCP tool at handoff.
|
|
44
|
+
3. **Specialist sequence**. dispatch plan with ordering/parallel markers.
|
|
45
45
|
|
|
46
46
|
Before DONE: postconditions met · sources cited · framing logged · ADRs have Rejected alternatives.
|
|
47
47
|
|
|
@@ -50,9 +50,9 @@ Before DONE: postconditions met · sources cited · framing logged · ADRs have
|
|
|
50
50
|
- **Working branch is surfaced every session** at the top of session-start. Restate it before any mutating operation.
|
|
51
51
|
- **Never commit, push, or merge without asking first.** Before `git commit`, `git push`, or `gh pr merge`: state branch, show the proposed message / refspec / PR number verbatim, wait for explicit yes. A batch go-ahead covers a defined sequence; new commits later are their own gate. See `rules/common/commit-approval.md`.
|
|
52
52
|
|
|
53
|
-
##
|
|
53
|
+
## Intake surface
|
|
54
54
|
|
|
55
|
-
Session-start surfaces pending intake at `.cx/intake/pending/<id>.json`. Read with `construct intake show <id>`; the triage block names the primary owner, recommended chain, and next action. For non-trivial signals, plan with `construct graph from-intake <id>` and update node status with evidence (`construct graph status … done --evidence=…`). A node cannot reach `done` without an evidence record. Team / enterprise mode wraps tool calls in the MCP broker
|
|
55
|
+
The active profile (`construct profile show`) sets the intake taxonomy. Session-start surfaces pending intake at `.cx/intake/pending/<id>.json`. Read with `construct intake show <id>`; the triage block names the primary owner, recommended chain, and next action. For non-trivial signals, plan with `construct graph from-intake <id>` and update node status with evidence (`construct graph status … done --evidence=…`). A node cannot reach `done` without an evidence record. Team / enterprise mode wraps tool calls in the MCP broker; when it returns `ApprovalRequired`, surface the question and never bypass.
|
|
56
56
|
|
|
57
57
|
## Action discipline
|
|
58
58
|
|
|
@@ -73,15 +73,15 @@ Load-bearing state: `AGENTS.md`, `.cx/context.md`/`.json`, `docs/README.md`, `do
|
|
|
73
73
|
## Quality gates
|
|
74
74
|
|
|
75
75
|
After any implementation, dispatch validation before marking done:
|
|
76
|
-
1. cx-reviewer
|
|
77
|
-
2. cx-qa
|
|
76
|
+
1. cx-reviewer. correctness, regression, coverage
|
|
77
|
+
2. cx-qa. tests pass, coverage meets threshold
|
|
78
78
|
3. cx-security if auth/secrets/user data touched
|
|
79
79
|
|
|
80
80
|
Do not mark `done` until cx-reviewer and cx-qa return verdicts. BLOCKED or any CRITICAL finding stops shipping.
|
|
81
81
|
|
|
82
82
|
## Hard release gates
|
|
83
83
|
|
|
84
|
-
Run `npm run release:check` before any commit or push
|
|
84
|
+
Run `npm run release:check` before any commit or push. never wait for CI. Commits follow `.gitmessage`; PRs follow `.github/pull_request_template.md`. Full policy: `rules/common/release-gates.md`.
|
|
85
85
|
|
|
86
86
|
## Loop guard
|
|
87
87
|
|
|
@@ -91,6 +91,6 @@ Before stopping: surface incomplete tracker-linked plan slices and unmet accepta
|
|
|
91
91
|
|
|
92
92
|
## Drive mode
|
|
93
93
|
|
|
94
|
-
Activates on word-boundary triggers
|
|
94
|
+
Activates on word-boundary triggers. `/work:drive`, standalone `drive`, or `full send`. Substring matches do not count.
|
|
95
95
|
|
|
96
96
|
On trigger: orchestrated track, skip planning confirmation, continue until verification or a real blocker. State the dispatch plan upfront; brief status at phase transitions. User sees plan and outcomes, not deliberation.
|
|
@@ -460,6 +460,19 @@
|
|
|
460
460
|
],
|
|
461
461
|
"id": "stop:optimize-agents",
|
|
462
462
|
"description": "Check performance reviews and auto-optimize low-scoring agents at session end"
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
"matcher": "*",
|
|
466
|
+
"hooks": [
|
|
467
|
+
{
|
|
468
|
+
"type": "command",
|
|
469
|
+
"command": "node \"$HOME/.construct/lib/hooks/session-reflect.mjs\"",
|
|
470
|
+
"timeout": 5,
|
|
471
|
+
"async": true
|
|
472
|
+
}
|
|
473
|
+
],
|
|
474
|
+
"id": "stop:auto-reflect",
|
|
475
|
+
"description": "Deterministic session-end reflect: extracts a transcript summary into .cx/observations (500ms hard budget; CONSTRUCT_REFLECT_AUTO=off to disable)"
|
|
463
476
|
}
|
|
464
477
|
],
|
|
465
478
|
"UserPromptSubmit": [
|
package/rules/common/research.md
CHANGED
|
@@ -10,7 +10,31 @@ that depends on external facts or evolving internal evidence.
|
|
|
10
10
|
|
|
11
11
|
Construct treats research as a reproducible evidence-gathering process, not free-form browsing. If a claim could change decisions, scope, architecture, or roadmap, it must be tied to verifiable evidence.
|
|
12
12
|
|
|
13
|
-
## 1.
|
|
13
|
+
## 1. Recency discipline
|
|
14
|
+
|
|
15
|
+
Research always starts from the most recent available evidence.
|
|
16
|
+
|
|
17
|
+
- Default to sources from the current year before earlier years — always search most-recent-first.
|
|
18
|
+
- For fast-moving topics (AI tools, LLM behavior, cloud APIs, security advisories, market data), treat anything older than 12 months as presumptively stale unless a newer source confirms it is still accurate.
|
|
19
|
+
- When using a search engine or index, always filter or sort by date — do not rely on relevance ranking alone.
|
|
20
|
+
- State the publication or access date for every external source. If a source has no date, treat its confidence as `low` until recency is established another way.
|
|
21
|
+
|
|
22
|
+
## 2. Domain-specific starting points
|
|
23
|
+
|
|
24
|
+
Use the narrowest, most authoritative starting point for the research domain:
|
|
25
|
+
|
|
26
|
+
| Domain | Starting points (most recent first) |
|
|
27
|
+
|---|---|
|
|
28
|
+
| AI tools, LLM behavior, multi-agent | arXiv (cs.AI, cs.SE, cs.CL), ACL Anthology, NeurIPS/ICML/ICLR proceedings, then vendor research blogs |
|
|
29
|
+
| Developer tools, IDE, editor | Stack Overflow Developer Survey (current year), JetBrains Developer Survey, GitHub/Copilot blog, then product changelogs |
|
|
30
|
+
| Security, CVEs, supply chain | NVD, GitHub Security Advisories, OWASP, vendor security blogs (Google Project Zero, Microsoft Security Response), then ProjectDiscovery/Snyk reports |
|
|
31
|
+
| Market data, adoption, ARR | Primary company announcements or SEC filings, then TechCrunch/Bloomberg/WSJ (where citing company sources), then analyst reports |
|
|
32
|
+
| Cloud infra, APIs, SDKs | Official vendor docs for the exact version, changelog, migration guides |
|
|
33
|
+
| Regulatory, compliance, privacy | Primary regulation text, then official guidance from the issuing authority, then law firm analysis |
|
|
34
|
+
|
|
35
|
+
Tertiary sources (blogs, forums, Q&A, AI-generated summaries) may help locate primaries. They are not sufficient evidence for load-bearing claims.
|
|
36
|
+
|
|
37
|
+
## 3. Start order
|
|
14
38
|
|
|
15
39
|
Start with the narrowest authoritative source that can answer the question:
|
|
16
40
|
|
|
@@ -28,9 +52,7 @@ Start with the narrowest authoritative source that can answer the question:
|
|
|
28
52
|
4. **Tertiary sources last**
|
|
29
53
|
- blogs, forums, Q&A, analyst summaries, AI-generated summaries
|
|
30
54
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
## 2. Required metadata for every source
|
|
55
|
+
## 4. Required metadata for every source
|
|
34
56
|
|
|
35
57
|
Record:
|
|
36
58
|
|
|
@@ -40,9 +62,16 @@ Record:
|
|
|
40
62
|
- publication date, release date, or access date
|
|
41
63
|
- why this source is relevant
|
|
42
64
|
|
|
43
|
-
|
|
65
|
+
## 5. URL verification
|
|
66
|
+
|
|
67
|
+
Every URL cited in a committed document must be verified before the document is published.
|
|
68
|
+
|
|
69
|
+
- Fetch the URL. Confirm it resolves and the content matches the cited claim.
|
|
70
|
+
- Do not cite aggregate or index pages (e.g., arxiv.org search results, Google Scholar listings) for quantitative claims — cite the specific paper or article URL.
|
|
71
|
+
- If a URL returns a 404, paywall, or redirect loop, find the canonical source or replace the citation.
|
|
72
|
+
- Unverified URLs must be marked `[unverified]` until confirmed.
|
|
44
73
|
|
|
45
|
-
##
|
|
74
|
+
## 6. Verification rules
|
|
46
75
|
|
|
47
76
|
For each load-bearing claim:
|
|
48
77
|
|
|
@@ -54,7 +83,7 @@ For each load-bearing claim:
|
|
|
54
83
|
|
|
55
84
|
Claims about versions, APIs, security, pricing, compatibility, regulations, and timelines must cite the exact version/date basis.
|
|
56
85
|
|
|
57
|
-
##
|
|
86
|
+
## 7. Reproducibility
|
|
58
87
|
|
|
59
88
|
Research must be reproducible by another person in the repo.
|
|
60
89
|
|
|
@@ -67,7 +96,7 @@ Record:
|
|
|
67
96
|
|
|
68
97
|
If you cannot explain how the answer was obtained, the research is incomplete.
|
|
69
98
|
|
|
70
|
-
##
|
|
99
|
+
## 8. Evidence thresholds
|
|
71
100
|
|
|
72
101
|
Recommendations must state what evidence threshold was used.
|
|
73
102
|
|
|
@@ -81,27 +110,30 @@ Examples:
|
|
|
81
110
|
|
|
82
111
|
If the threshold is not met, the output should recommend more research, a weaker artifact, or a narrower decision.
|
|
83
112
|
|
|
84
|
-
##
|
|
113
|
+
## 9. Output standard
|
|
85
114
|
|
|
86
115
|
Research outputs should include:
|
|
87
116
|
|
|
88
117
|
- question
|
|
89
118
|
- method
|
|
90
|
-
- sources
|
|
119
|
+
- sources (with dates and classes)
|
|
91
120
|
- findings
|
|
92
121
|
- confidence
|
|
93
122
|
- open questions
|
|
94
123
|
- recommendation or next step
|
|
95
124
|
|
|
96
|
-
Every substantive finding should point to a source path, URL, or document reference.
|
|
125
|
+
Every substantive finding should point to a verified source path, URL, or document reference.
|
|
97
126
|
|
|
98
|
-
##
|
|
127
|
+
## 10. Anti-patterns
|
|
99
128
|
|
|
100
129
|
Do not:
|
|
101
130
|
|
|
131
|
+
- start research from older years when more-recent sources are available
|
|
102
132
|
- stop at the first plausible answer
|
|
103
133
|
- cite a blog when the spec or source code is available
|
|
134
|
+
- cite an aggregate or index page when the specific document is available
|
|
104
135
|
- present inference as if the source said it directly
|
|
105
136
|
- ignore conflicting evidence
|
|
106
137
|
- use stale undated material for fast-moving topics without saying so
|
|
107
138
|
- promote weak product evidence into committed requirements
|
|
139
|
+
- include URLs that have not been fetched and confirmed
|
package/scripts/sync-agents.mjs
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
} from "../lib/mcp-platform-config.mjs";
|
|
39
39
|
import { loadConstructEnv } from "../lib/env-config.mjs";
|
|
40
40
|
import { inlineRoleAntiPatterns, PROMPT_WORD_CAP } from "../lib/role-preload.mjs";
|
|
41
|
+
import { resolveActiveProfile } from "../lib/profiles/loader.mjs";
|
|
41
42
|
import { resolveTiersForPrimary } from "../lib/model-router.mjs";
|
|
42
43
|
import { stampFrontmatter } from "../lib/doc-stamp.mjs";
|
|
43
44
|
|
|
@@ -499,7 +500,17 @@ function buildEntries() {
|
|
|
499
500
|
});
|
|
500
501
|
}
|
|
501
502
|
|
|
503
|
+
// Filter agents by the active profile's role set. RND lists every specialist
|
|
504
|
+
// so behavior is unchanged for the default; non-RND profiles emit only the
|
|
505
|
+
// specialists they declare. Profiles whose role list is empty (legacy/test)
|
|
506
|
+
// fall through to no filter so callers don't get a silent empty registry.
|
|
507
|
+
const activeProfile = resolveActiveProfile(process.cwd());
|
|
508
|
+
const profileRoles = Array.isArray(activeProfile?.roles) && activeProfile.roles.length > 0
|
|
509
|
+
? new Set(activeProfile.roles)
|
|
510
|
+
: null;
|
|
511
|
+
|
|
502
512
|
for (const agent of registry.agents ?? []) {
|
|
513
|
+
if (profileRoles && !profileRoles.has(agent.name)) continue;
|
|
503
514
|
entries.push({
|
|
504
515
|
...agent,
|
|
505
516
|
isPersona: false,
|