@geraldmaron/construct 1.0.17 → 1.0.19
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 +6 -4
- package/bin/construct +26 -3
- package/commands/design/flow.md +2 -0
- package/commands/design/ui.md +2 -0
- package/db/schema/003_observation_reconciliation.sql +14 -0
- package/lib/bootstrap/resources.mjs +0 -1
- package/lib/cli-commands.mjs +33 -5
- package/lib/comment-lint.mjs +44 -0
- package/lib/contracts/validate.mjs +106 -0
- package/lib/decisions/enforced-baseline.json +23 -0
- package/lib/decisions/golden.mjs +87 -0
- package/lib/decisions/precedence.mjs +46 -0
- package/lib/decisions/registry.mjs +469 -0
- package/lib/deployment/parity-contract.mjs +148 -0
- package/lib/embed/cli.mjs +11 -0
- package/lib/embed/conflict-detection.mjs +4 -4
- package/lib/embed/customer-profiles.mjs +1 -1
- package/lib/embed/reconcile.mjs +60 -0
- package/lib/embed/semantic.mjs +5 -3
- package/lib/gates-audit.mjs +2 -2
- package/lib/hooks/config-protection.mjs +22 -3
- package/lib/hooks/guard-bash.mjs +1 -1
- package/lib/init-docs.mjs +1 -0
- package/lib/mode-commands.mjs +6 -8
- package/lib/observation-store.mjs +16 -2
- package/lib/opencode-telemetry.mjs +1 -1
- package/lib/roles/cli.mjs +10 -2
- package/lib/roles/gateway.mjs +50 -1
- package/lib/scheduler/index.mjs +31 -0
- package/lib/server/index.mjs +13 -3
- package/lib/server/static/index.html +1 -1
- package/lib/setup.mjs +6 -0
- package/lib/storage/hybrid-query.mjs +49 -38
- package/lib/storage/rrf.mjs +42 -0
- package/lib/storage/vector-client.mjs +18 -3
- package/lib/telemetry/backends/local.mjs +1 -1
- package/lib/telemetry/skill-calls.mjs +1 -1
- package/lib/templates/visual-requirements.mjs +84 -0
- package/package.json +12 -5
- package/rules/common/comments.md +3 -0
- package/rules/common/no-fabrication.md +3 -0
- package/rules/common/precedence.md +19 -0
- package/rules/common/research-sources.md +32 -0
- package/rules/common/research.md +59 -2
- package/skills/roles/data-engineer.pipeline.md +13 -1
- package/skills/roles/debugger.md +9 -0
- package/skills/roles/designer.accessibility.md +13 -3
- package/skills/roles/designer.md +10 -0
- package/skills/roles/engineer.platform.md +8 -0
- package/skills/roles/operator.md +10 -1
- package/skills/roles/operator.release.md +8 -0
- package/skills/roles/operator.sre.md +10 -1
- package/skills/roles/orchestrator.md +9 -2
- package/skills/roles/product-manager.business-strategy.md +10 -1
- package/skills/roles/researcher.explorer.md +12 -1
- package/skills/roles/researcher.ux.md +13 -1
- package/skills/roles/reviewer.devil-advocate.md +14 -2
- package/skills/roles/reviewer.evaluator.md +17 -4
- package/skills/roles/reviewer.trace.md +12 -1
- package/skills/roles/security.legal-compliance.md +8 -0
- package/skills/roles/security.md +11 -0
- package/specialists/contracts.json +18 -0
- package/specialists/prompts/cx-architect.md +1 -0
- package/specialists/prompts/cx-designer.md +1 -1
- package/specialists/prompts/cx-operations.md +1 -0
- package/specialists/prompts/cx-rd-lead.md +1 -0
- package/specialists/prompts/cx-researcher.md +4 -2
- package/specialists/prompts/cx-sre.md +1 -0
- package/specialists/registry.json +2 -2
- package/templates/docs/backlog-proposal.md +1 -1
- package/templates/docs/customer-profile.md +1 -1
- package/templates/docs/evidence-brief.md +5 -1
- package/templates/docs/incident-report.md +37 -21
- package/templates/docs/prfaq.md +2 -2
- package/templates/docs/product-intelligence-report.md +1 -1
- package/templates/docs/research-brief.md +8 -6
- package/templates/docs/research-finding.md +32 -7
- package/templates/docs/rfc.md +13 -1
- package/templates/docs/runbook.md +20 -1
- package/templates/docs/signal-brief.md +4 -1
- package/templates/docs/skill-artifact.md +27 -7
- package/templates/docs/strategy.md +23 -2
- package/templates/docs/test-plan.md +96 -0
- package/lib/bootstrap/lazy-install.mjs +0 -161
- package/lib/embed/jobs/vector-sync.mjs +0 -198
- package/lib/knowledge/postgres-search.mjs +0 -132
- package/lib/services/pattern-promotion-service.mjs +0 -167
- package/lib/storage/unified-storage.mjs +0 -550
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/reconcile.mjs — bring the Postgres observation index back in sync
|
|
3
|
+
* with the local source of truth.
|
|
4
|
+
*
|
|
5
|
+
* Observations are embedded inline on write (lib/observation-store.mjs), but
|
|
6
|
+
* the inline write is skipped whenever Postgres is unreachable at creation time
|
|
7
|
+
* (the local hashing-bow fallback is used instead), and a model change leaves
|
|
8
|
+
* every prior row embedded in a stale vector space. This pass is the safety
|
|
9
|
+
* net: for each local observation it compares the (content_hash, model) the
|
|
10
|
+
* pg row was embedded with against the live content + current model, and
|
|
11
|
+
* re-embeds ONLY the rows that are missing or stale. Idempotent — a second run
|
|
12
|
+
* with no changes re-embeds nothing.
|
|
13
|
+
*
|
|
14
|
+
* Reconciliation-on-sync is preferred over a fixed-interval polling cron (see
|
|
15
|
+
* docs/research/vector-search-best-practices.md): the pass runs where Construct
|
|
16
|
+
* already touches storage (the model-change re-embed, the storage CLI), keys
|
|
17
|
+
* idempotency on a content hash, and re-embeds on model change.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { VectorClient } from '../storage/vector-client.mjs';
|
|
21
|
+
import { embedText as embedTextEngine, getEmbeddingModelInfo } from '../storage/embeddings-engine.mjs';
|
|
22
|
+
import { listObservations, getObservation, observationSearchText, observationContentHash } from '../observation-store.mjs';
|
|
23
|
+
|
|
24
|
+
export async function reconcileObservationEmbeddings(rootDir, { env = process.env, limit = Infinity, embed = embedTextEngine, modelId = null } = {}) {
|
|
25
|
+
const client = new VectorClient({ env });
|
|
26
|
+
const result = { checked: 0, reembedded: 0, model: null, skipped: null };
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
if (!(await client.isHealthy()) || !(await client.isPgvectorEnabled())) {
|
|
30
|
+
result.skipped = 'no-pg';
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const currentModel = modelId || (await getEmbeddingModelInfo({ env })).model;
|
|
35
|
+
result.model = currentModel;
|
|
36
|
+
|
|
37
|
+
const ids = (listObservations(rootDir, { limit: Number.MAX_SAFE_INTEGER }) || []).map((e) => e.id);
|
|
38
|
+
const fingerprints = await client.getObservationFingerprints(ids);
|
|
39
|
+
|
|
40
|
+
for (const id of ids) {
|
|
41
|
+
if (result.reembedded >= limit) break;
|
|
42
|
+
const obs = getObservation(rootDir, id);
|
|
43
|
+
if (!obs) continue;
|
|
44
|
+
result.checked += 1;
|
|
45
|
+
|
|
46
|
+
const searchText = observationSearchText(obs);
|
|
47
|
+
const hash = observationContentHash(searchText);
|
|
48
|
+
const fp = fingerprints.get(id);
|
|
49
|
+
if (fp && fp.contentHash === hash && fp.model === currentModel) continue;
|
|
50
|
+
|
|
51
|
+
const { embedding, model } = await embed(searchText, { env });
|
|
52
|
+
await client.storeObservation({ ...obs, embedding, contentHash: hash, model: model || currentModel });
|
|
53
|
+
result.reembedded += 1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return result;
|
|
57
|
+
} finally {
|
|
58
|
+
await client.close();
|
|
59
|
+
}
|
|
60
|
+
}
|
package/lib/embed/semantic.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/embed/semantic.mjs — Semantic analysis for intake signals.
|
|
3
3
|
*
|
|
4
|
-
* Generates embeddings via @
|
|
4
|
+
* Generates embeddings via @huggingface/transformers, caches them to disk,
|
|
5
5
|
* computes cosine similarity, and clusters related signals by topic.
|
|
6
6
|
* All local — no external API calls. Default model is all-MiniLM-L6-v2
|
|
7
7
|
* (384-dimensional vectors, ~50MB on disk, fast inference).
|
|
@@ -23,7 +23,7 @@ import { homedir } from 'node:os';
|
|
|
23
23
|
|
|
24
24
|
const CACHE_DIR = join(homedir(), '.cx', 'cache', 'embeddings');
|
|
25
25
|
const INDEX_FILE = join(CACHE_DIR, 'index.json');
|
|
26
|
-
const MODEL_NAME = 'all-MiniLM-L6-v2';
|
|
26
|
+
const MODEL_NAME = 'Xenova/all-MiniLM-L6-v2';
|
|
27
27
|
const DIMENSIONS = 384;
|
|
28
28
|
const LOG = process.env.CONSTRUCT_DEBUG_EMBED === '1';
|
|
29
29
|
|
|
@@ -35,7 +35,9 @@ let embedder = null;
|
|
|
35
35
|
async function getEmbedder() {
|
|
36
36
|
if (embedder !== null) return embedder;
|
|
37
37
|
try {
|
|
38
|
-
const { pipeline } = await import('@
|
|
38
|
+
const { pipeline, env: hfEnv } = await import('@huggingface/transformers');
|
|
39
|
+
hfEnv.allowLocalModels = true;
|
|
40
|
+
hfEnv.useBrowserCache = false;
|
|
39
41
|
embedder = await pipeline('feature-extraction', MODEL_NAME, {
|
|
40
42
|
quantized: true,
|
|
41
43
|
});
|
package/lib/gates-audit.mjs
CHANGED
|
@@ -76,8 +76,8 @@ function parseCIJobs(rootDir) {
|
|
|
76
76
|
const path = join(dir, file);
|
|
77
77
|
if (!existsSync(path)) continue;
|
|
78
78
|
const yml = readFileSync(path, 'utf8');
|
|
79
|
-
const jobNames = [...yml.matchAll(/^
|
|
80
|
-
const stepNames = [...yml.matchAll(/^
|
|
79
|
+
const jobNames = [...yml.matchAll(/^ {4}name:\s*(.+?)\s*$/gm)].map((m) => m[1].trim());
|
|
80
|
+
const stepNames = [...yml.matchAll(/^ {6}- name:\s*(.+?)\s*$/gm)].map((m) => m[1].trim());
|
|
81
81
|
names.push(...jobNames, ...stepNames);
|
|
82
82
|
}
|
|
83
83
|
return names.map((n) => n.replace(/\$\{\{\s*matrix\.(\w+)\s*\}\}/g, (_, k) => `<${k}>`));
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
* lib/hooks/config-protection.mjs — protects code-quality config from being weakened.
|
|
4
4
|
*
|
|
5
5
|
* Runs as PreToolUse on Edit|Write|MultiEdit. Blocks edits to eslint /
|
|
6
|
-
* prettier / tsconfig / biome / stylelint configs
|
|
7
|
-
*
|
|
6
|
+
* prettier / tsconfig / biome / stylelint configs — but only once the config
|
|
7
|
+
* is an established contract, i.e. tracked in git. A config that is not yet
|
|
8
|
+
* tracked is being introduced or authored: first-time setup and pre-commit
|
|
9
|
+
* iteration are allowed; weakening a committed config stays blocked.
|
|
8
10
|
*
|
|
9
11
|
* Meta-system files (hooks, registry, settings template, install/setup
|
|
10
12
|
* entrypoints) are audited but NOT blocked. Editing them is normal work on
|
|
@@ -22,6 +24,8 @@
|
|
|
22
24
|
* @exits 0 = pass | 2 = block tool call
|
|
23
25
|
*/
|
|
24
26
|
import { readFileSync } from 'fs';
|
|
27
|
+
import { execFileSync } from 'child_process';
|
|
28
|
+
import { dirname } from 'path';
|
|
25
29
|
import { logHookFailure } from './_lib/log.mjs';
|
|
26
30
|
|
|
27
31
|
const filePath = process.env.TOOL_INPUT_FILE_PATH || (() => {
|
|
@@ -56,6 +60,21 @@ const META_FILES = [
|
|
|
56
60
|
/(?:^|\/)tsconfig(\.[^/]+)?\.json$/i,
|
|
57
61
|
];
|
|
58
62
|
|
|
63
|
+
// A config is a contract worth protecting only once it is committed. `git
|
|
64
|
+
// ls-files --error-unmatch` exits 0 for a tracked path and non-zero otherwise,
|
|
65
|
+
// so an untracked (newly introduced) config is allowed through.
|
|
66
|
+
|
|
67
|
+
function isTrackedInGit(p) {
|
|
68
|
+
try {
|
|
69
|
+
execFileSync('git', ['ls-files', '--error-unmatch', p], {
|
|
70
|
+
cwd: dirname(p), stdio: 'ignore', timeout: 1000,
|
|
71
|
+
});
|
|
72
|
+
return true;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
59
78
|
async function emitEvent(type, category, summary) {
|
|
60
79
|
try {
|
|
61
80
|
const { emitRoleEvent } = await import('../roles/hook-emit.mjs');
|
|
@@ -69,7 +88,7 @@ async function emitEvent(type, category, summary) {
|
|
|
69
88
|
}
|
|
70
89
|
|
|
71
90
|
const base = filePath.split('/').pop();
|
|
72
|
-
if (PROTECTED.some(r => r.test(base))) {
|
|
91
|
+
if (PROTECTED.some(r => r.test(base)) && isTrackedInGit(filePath)) {
|
|
73
92
|
process.stderr.write(
|
|
74
93
|
`[config-protection] The code quality rules are protected. Fix the code to meet the existing standards — don't weaken the rules.\nFile: ${filePath}\n`
|
|
75
94
|
);
|
package/lib/hooks/guard-bash.mjs
CHANGED
|
@@ -136,7 +136,7 @@ if (process.env.CONSTRUCT_ROLES !== 'off') {
|
|
|
136
136
|
verdict = checkAction({ personaId, action: 'bash', target: trimmed });
|
|
137
137
|
}
|
|
138
138
|
if (!verdict.allowed && verdict.reason === 'outside-fence') {
|
|
139
|
-
const allowedList = (() => {
|
|
139
|
+
const allowedList = await (async () => {
|
|
140
140
|
try { const { loadManifest } = await import('../roles/manifest.mjs'); const m = loadManifest(personaId); return m?.fence?.allowedCommands?.join(', ') || 'see role-manifests.json'; } catch { return 'see role-manifests.json'; }
|
|
141
141
|
})();
|
|
142
142
|
process.stderr.write(
|
package/lib/init-docs.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import { fileURLToPath } from "node:url";
|
|
|
25
25
|
|
|
26
26
|
import { suggestDocsLaneForFile } from './docs-routing.mjs';
|
|
27
27
|
import { stampFrontmatter } from "./doc-stamp.mjs";
|
|
28
|
+
import { multiSelect, selectOption } from './tty-prompts.mjs';
|
|
28
29
|
import readline from "node:readline";
|
|
29
30
|
|
|
30
31
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
package/lib/mode-commands.mjs
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
// Standardized command contracts across deployment modes
|
|
10
10
|
|
|
11
11
|
import { spawnSync } from 'node:child_process';
|
|
12
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
13
|
+
import { basename, join } from 'node:path';
|
|
12
14
|
|
|
13
15
|
export const MODE_COMMANDS = {
|
|
14
16
|
// Intake queue operations
|
|
@@ -65,19 +67,15 @@ function getDeploymentMode() {
|
|
|
65
67
|
function parseIntakeFiles(stdout) {
|
|
66
68
|
return stdout.split('\n')
|
|
67
69
|
.filter(Boolean)
|
|
68
|
-
.map(f => ({ id:
|
|
70
|
+
.map(f => ({ id: basename(f, '.json'), source: 'file' }));
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
function readIntakeFile(id, options) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
const filePath = path.join(options.cwd || process.cwd(), '.cx/intake/pending', `${id}.json`);
|
|
75
|
-
|
|
76
|
-
if (!fs.existsSync(filePath)) {
|
|
74
|
+
const filePath = join(options.cwd || process.cwd(), '.cx/intake/pending', `${id}.json`);
|
|
75
|
+
if (!existsSync(filePath)) {
|
|
77
76
|
return { error: `Intake item ${id} not found` };
|
|
78
77
|
}
|
|
79
|
-
|
|
80
|
-
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
78
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
async function queryPostgresIntake(options) {
|
|
@@ -25,6 +25,18 @@ const OBS_DIR = '.cx/observations';
|
|
|
25
25
|
const INDEX_FILE = 'index.json';
|
|
26
26
|
const VECTORS_FILE = 'vectors.json';
|
|
27
27
|
const MAX_INDEX = 1000;
|
|
28
|
+
|
|
29
|
+
// The exact text fed to the embedder, and its content hash. addObservation and
|
|
30
|
+
// the reconciliation pass MUST derive both the same way, so the (content_hash,
|
|
31
|
+
// model) fingerprint they compare is computed identically.
|
|
32
|
+
|
|
33
|
+
export function observationSearchText(obs) {
|
|
34
|
+
return [obs.summary, obs.content, ...(obs.tags || [])].filter(Boolean).join(' ');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function observationContentHash(text) {
|
|
38
|
+
return crypto.createHash('sha256').update(String(text || '')).digest('hex');
|
|
39
|
+
}
|
|
28
40
|
const MAX_SUMMARY = 500;
|
|
29
41
|
const MAX_CONTENT = 2000;
|
|
30
42
|
const MAX_TAGS = 10;
|
|
@@ -214,11 +226,13 @@ export async function addObservation(rootDir, {
|
|
|
214
226
|
try {
|
|
215
227
|
const client = new VectorClient();
|
|
216
228
|
if (await client.isHealthy() && await client.isPgvectorEnabled()) {
|
|
217
|
-
const searchText =
|
|
218
|
-
const { embedding } = await embedTextEngine(searchText);
|
|
229
|
+
const searchText = observationSearchText(record);
|
|
230
|
+
const { embedding, model } = await embedTextEngine(searchText);
|
|
219
231
|
await client.storeObservation({
|
|
220
232
|
...record,
|
|
221
233
|
embedding,
|
|
234
|
+
contentHash: observationContentHash(searchText),
|
|
235
|
+
model,
|
|
222
236
|
});
|
|
223
237
|
await client.close();
|
|
224
238
|
return record;
|
|
@@ -29,7 +29,7 @@ export function getIngestClient(env = process.env) {
|
|
|
29
29
|
rootDir: env.CX_TOOLKIT_DIR,
|
|
30
30
|
onError: (err) => {
|
|
31
31
|
if (env.CONSTRUCT_TRACE_DEBUG === "1") {
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
console.error("[construct-telemetry]", err?.message || err);
|
|
34
34
|
}
|
|
35
35
|
},
|
package/lib/roles/cli.mjs
CHANGED
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
* latest — show the most recent unresolved invocation (full brief)
|
|
7
7
|
* show <fingerprint> — show one invocation by fingerprint
|
|
8
8
|
* status — show onboarded personas + their event types
|
|
9
|
+
* resolve <fp> — mark one invocation resolved
|
|
10
|
+
* prune — compact the queue: drop resolved + TTL-expired entries
|
|
9
11
|
* reset — clear the pending queue (manual recovery)
|
|
10
12
|
*/
|
|
11
13
|
|
|
12
14
|
import { listOnboardedPersonas, loadManifest } from './manifest.mjs';
|
|
13
|
-
import { listPending, markResolved, resetPending } from './gateway.mjs';
|
|
15
|
+
import { listPending, markResolved, resetPending, prunePending } from './gateway.mjs';
|
|
14
16
|
import { recent as recentEvents } from './event-bus.mjs';
|
|
15
17
|
|
|
16
18
|
function fmtTime(ts) {
|
|
@@ -106,6 +108,12 @@ export async function runCli(args) {
|
|
|
106
108
|
return ok ? 0 : 1;
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
if (sub === 'prune') {
|
|
112
|
+
const { removed, resolved, expired, fixtures, kept } = prunePending();
|
|
113
|
+
console.log(`Pruned ${removed} entr${removed === 1 ? 'y' : 'ies'} (${resolved} resolved, ${expired} expired, ${fixtures} fixture) · ${kept} remaining.`);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
109
117
|
if (sub === 'reset') {
|
|
110
118
|
resetPending();
|
|
111
119
|
console.log('Pending queue cleared.');
|
|
@@ -113,6 +121,6 @@ export async function runCli(args) {
|
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
console.error(`Unknown role subcommand: ${sub}`);
|
|
116
|
-
console.error('Usage: construct role [list|latest|show <fp>|status|resolve <fp>|reset]');
|
|
124
|
+
console.error('Usage: construct role [list|latest|show <fp>|status|resolve <fp>|prune|reset]');
|
|
117
125
|
return 2;
|
|
118
126
|
}
|
package/lib/roles/gateway.mjs
CHANGED
|
@@ -30,6 +30,7 @@ const DEFAULTS = {
|
|
|
30
30
|
thresholdWindowMs: 10 * 60 * 1000,
|
|
31
31
|
cooldownMs: 30 * 60 * 1000,
|
|
32
32
|
rateCeilingPerHour: 3,
|
|
33
|
+
pendingTtlMs: 14 * 24 * 60 * 60 * 1000,
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
function ensureDir() {
|
|
@@ -226,10 +227,58 @@ export async function recordAndMaybeInvoke(eventType, payload = {}) {
|
|
|
226
227
|
return { recorded: true, ...r };
|
|
227
228
|
}
|
|
228
229
|
|
|
230
|
+
// A surfaced role invocation is a prompt to act, not a durable record (bd owns
|
|
231
|
+
// the durable issue). Unresolved entries older than the TTL are stale noise —
|
|
232
|
+
// e.g. pre-fixture-guard escalations that would otherwise surface at every
|
|
233
|
+
// session-start forever — so they drop out of listings and never expire bd work.
|
|
234
|
+
|
|
235
|
+
function isExpired(entry, now) {
|
|
236
|
+
return typeof entry.ts === 'number' && (now - entry.ts) > DEFAULTS.pendingTtlMs;
|
|
237
|
+
}
|
|
238
|
+
|
|
229
239
|
export function listPending({ unresolved = true } = {}) {
|
|
230
240
|
const entries = readPending();
|
|
231
241
|
if (!unresolved) return entries;
|
|
232
|
-
|
|
242
|
+
const now = Date.now();
|
|
243
|
+
return entries.filter((e) => !e.resolvedAt && !isExpired(e, now));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// An entry that names an OS-tmp path is a pre-guard test-fixture escalation
|
|
247
|
+
// (shouldEscalate now blocks these at the source). Sweep them on prune so the
|
|
248
|
+
// historical noise — e.g. the cx-secrets fixtures — clears immediately rather
|
|
249
|
+
// than waiting out the TTL.
|
|
250
|
+
|
|
251
|
+
function referencesFixture(entry) {
|
|
252
|
+
for (const field of [entry.cwd, entry.project, entry.summary]) {
|
|
253
|
+
if (typeof field !== 'string') continue;
|
|
254
|
+
for (const token of field.split(/\s+/)) {
|
|
255
|
+
if (isTestFixturePath(token.replace(/[)\].,:;]+$/, ''))) return true;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Compact the queue on disk: drop resolved, TTL-expired, and fixture-path
|
|
262
|
+
// entries. Idempotent; returns counts so the CLI and session-start can report
|
|
263
|
+
// what was reclaimed.
|
|
264
|
+
|
|
265
|
+
export function prunePending({ now = Date.now() } = {}) {
|
|
266
|
+
const entries = readPending();
|
|
267
|
+
const kept = [];
|
|
268
|
+
let resolved = 0;
|
|
269
|
+
let expired = 0;
|
|
270
|
+
let fixtures = 0;
|
|
271
|
+
for (const e of entries) {
|
|
272
|
+
if (e.resolvedAt) { resolved += 1; continue; }
|
|
273
|
+
if (isExpired(e, now)) { expired += 1; continue; }
|
|
274
|
+
if (referencesFixture(e)) { fixtures += 1; continue; }
|
|
275
|
+
kept.push(e);
|
|
276
|
+
}
|
|
277
|
+
const removed = resolved + expired + fixtures;
|
|
278
|
+
if (removed > 0) {
|
|
279
|
+
writeFileSync(pendingPath(), kept.length ? kept.map((e) => JSON.stringify(e)).join('\n') + '\n' : '');
|
|
280
|
+
}
|
|
281
|
+
return { removed, resolved, expired, fixtures, kept: kept.length };
|
|
233
282
|
}
|
|
234
283
|
|
|
235
284
|
// Drain events that accumulated outside a Claude session. Walks recent
|
package/lib/scheduler/index.mjs
CHANGED
|
@@ -130,3 +130,34 @@ registerJob({
|
|
|
130
130
|
};
|
|
131
131
|
},
|
|
132
132
|
});
|
|
133
|
+
|
|
134
|
+
// The prompt-optimize loop reads telemetry traces and proposes prompt patches.
|
|
135
|
+
// Scheduled runs are advisory by construction: the argv never carries --apply, so
|
|
136
|
+
// a cadence surfaces proposals for review and can never auto-mutate a prompt. The
|
|
137
|
+
// existing --apply gate in scripts/optimize.mjs remains the only path that writes.
|
|
138
|
+
|
|
139
|
+
export const OPTIMIZE_JOB_ID = 'optimize-loop';
|
|
140
|
+
|
|
141
|
+
export function optimizeJobArgv() {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
registerJob({
|
|
146
|
+
id: OPTIMIZE_JOB_ID,
|
|
147
|
+
schedule: '0 5 * * 1',
|
|
148
|
+
mode: 'solo',
|
|
149
|
+
handler: async ({ cwd, env }) => {
|
|
150
|
+
const argv = optimizeJobArgv();
|
|
151
|
+
if (argv.includes('--apply')) throw new Error('scheduled optimize must never auto-apply');
|
|
152
|
+
const { execFileSync } = await import('node:child_process');
|
|
153
|
+
const { fileURLToPath } = await import('node:url');
|
|
154
|
+
const { resolve, dirname } = await import('node:path');
|
|
155
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
156
|
+
try {
|
|
157
|
+
const output = execFileSync('node', [resolve(root, 'scripts', 'optimize.mjs'), ...argv], { cwd, env, encoding: 'utf8' });
|
|
158
|
+
return { status: 'ok', applied: false, output: output.slice(0, 500) };
|
|
159
|
+
} catch (err) {
|
|
160
|
+
return { status: 'error', applied: false, error: err.message };
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
});
|
package/lib/server/index.mjs
CHANGED
|
@@ -713,9 +713,19 @@ const server = createServer(async (req, res) => {
|
|
|
713
713
|
return;
|
|
714
714
|
}
|
|
715
715
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
716
|
+
// Provider-agnostic telemetry-UI sign-in. An external backend configured via
|
|
717
|
+
// CONSTRUCT_TELEMETRY_URL uses the vendor-neutral bridge; with none set, fall
|
|
718
|
+
// back to the zero-touch local Langfuse stack. `/langfuse/login` stays as a
|
|
719
|
+
// back-compat alias of the canonical `/telemetry/login`.
|
|
720
|
+
|
|
721
|
+
if ((url.pathname === '/api/services/telemetry/login' || url.pathname === '/api/services/langfuse/login') && req.method === 'GET') {
|
|
722
|
+
if (process.env.CONSTRUCT_TELEMETRY_URL) {
|
|
723
|
+
const { handleTelemetryLogin } = await import('./telemetry-login.mjs');
|
|
724
|
+
await handleTelemetryLogin(req, res, {});
|
|
725
|
+
} else {
|
|
726
|
+
const { handleLangfuseLogin } = await import('./langfuse-login.mjs');
|
|
727
|
+
await handleLangfuseLogin(req, res);
|
|
728
|
+
}
|
|
719
729
|
return;
|
|
720
730
|
}
|
|
721
731
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><!--qA_CTDVxjnG_0_bArvNYn--><html lang="en" data-theme="dark" data-density="comfortable" data-motion="normal" class="__variable_629dad __variable_246ccd __variable_3c557b"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/22a5144ee8d83bca-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="preload" href="/_next/static/media/bb3ef058b751a6ad-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/47a506ef57811fb9.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-8e0503f8a36190b3.js"/><script src="/_next/static/chunks/87c73c54-24122e7b92478d00.js" async=""></script><script src="/_next/static/chunks/18-1e25b5692ec1d26d.js" async=""></script><script src="/_next/static/chunks/main-app-07d82ec6c92d7402.js" async=""></script><script src="/_next/static/chunks/9664-af80478aa73ba424.js" async=""></script><script src="/_next/static/chunks/8144-da56a4a291c0b21a.js" async=""></script><script src="/_next/static/chunks/app/layout-1a2d2674aa220229.js" async=""></script><script src="/_next/static/chunks/app/page-2568fbdd2b0e1eca.js" async=""></script><meta name="next-size-adjust" content=""/><title>Construct — Dashboard</title><meta name="description" content="Local operations dashboard for Construct — approvals, health, knowledge, models, providers."/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div class="shell"><header class="topbar"><a class="brand" href="/"><div class="mark"></div><div class="name">Construct<em>dashboard</em></div></a><div class="search-wrap"><label class="search"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"></circle><line x1="21" y1="21" x2="16.5" y2="16.5"></line></svg><input readOnly="" placeholder="Search dashboard…"/><span class="kbd">⌘K</span></label></div><div class="top-actions"><button class="icon-btn" title="Toggle theme" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"></circle><line x1="12" y1="2" x2="12" y2="5"></line><line x1="12" y1="19" x2="12" y2="22"></line><line x1="2" y1="12" x2="5" y2="12"></line><line x1="19" y1="12" x2="22" y2="12"></line><line x1="4.5" y1="4.5" x2="6.5" y2="6.5"></line><line x1="17.5" y1="17.5" x2="19.5" y2="19.5"></line><line x1="4.5" y1="19.5" x2="6.5" y2="17.5"></line><line x1="17.5" y1="6.5" x2="19.5" y2="4.5"></line></svg></button><button class="icon-btn" title="Toggle density" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="7" x2="21" y2="7"></line><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="17" x2="21" y2="17"></line></svg></button><a class="icon-btn outlined" href="https://github.com/geraldmaron/construct" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M12 .5C5.65.5.5 5.66.5 12.02c0 5.09 3.29 9.4 7.86 10.92.58.1.79-.25.79-.55v-2.13c-3.2.7-3.87-1.37-3.87-1.37-.52-1.32-1.27-1.67-1.27-1.67-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.76 2.69 1.25 3.35.96.1-.74.4-1.25.73-1.54-2.55-.29-5.24-1.28-5.24-5.7 0-1.26.45-2.29 1.18-3.1-.12-.29-.51-1.46.11-3.04 0 0 .96-.31 3.16 1.18a10.9 10.9 0 0 1 5.74 0c2.2-1.49 3.16-1.18 3.16-1.18.62 1.58.23 2.75.12 3.04.74.81 1.18 1.84 1.18 3.1 0 4.43-2.69 5.41-5.25 5.69.41.36.78 1.05.78 2.13v3.16c0 .31.21.66.79.55A11.52 11.52 0 0 0 23.5 12.02C23.5 5.66 18.35.5 12 .5Z"></path></svg> <span style="font-size:11px">repo</span></a></div><div class="progress"></div></header><div class="body-grid"><aside class="sidebar"><div class="side-group"><div class="side-label">Overview</div><a class="side-link active" href="/"><span class="num">01</span><span>Home</span></a><a class="side-link" href="/doctor/"><span class="num">02</span><span>Doctor</span></a><a class="side-link" href="/resources/"><span class="num">03</span><span>Services</span></a></div><div class="side-group"><div class="side-label">Activity</div><a class="side-link" href="/audit/"><span class="num">01</span><span>Audit trail</span></a><a class="side-link" href="/snapshots/"><span class="num">02</span><span>Snapshots</span></a><a class="side-link" href="/performance/"><span class="num">03</span><span>Performance</span></a></div><div class="side-group"><div class="side-label">Work</div><a class="side-link" href="/approvals/"><span class="num">01</span><span>Approvals</span></a><a class="side-link" href="/workflow/"><span class="num">02</span><span>Workflow</span></a><a class="side-link" href="/beads/"><span class="num">03</span><span>Beads</span></a><a class="side-link" href="/artifacts/"><span class="num">04</span><span>Artifacts</span></a><a class="side-link" href="/intake/"><span class="num">05</span><span>Intake</span></a></div><div class="side-group"><div class="side-label">Specialists</div><a class="side-link" href="/agents/"><span class="num">01</span><span>Specialists</span></a><a class="side-link" href="/skills/"><span class="num">02</span><span>Skills</span></a><a class="side-link" href="/commands/"><span class="num">03</span><span>Slash commands</span></a><a class="side-link" href="/hooks/"><span class="num">04</span><span>Hooks</span></a><a class="side-link" href="/plugins/"><span class="num">05</span><span>Plugins</span></a></div><div class="side-group"><div class="side-label">Models & Providers</div><a class="side-link" href="/models/"><span class="num">01</span><span>Models</span></a><a class="side-link" href="/providers/"><span class="num">02</span><span>Providers</span></a><a class="side-link" href="/mcp/"><span class="num">03</span><span>MCP servers</span></a></div><div class="side-group"><div class="side-label">Knowledge</div><a class="side-link" href="/knowledge/"><span class="num">01</span><span>Knowledge</span></a><a class="side-link" href="/editor/"><span class="num">02</span><span>Editor</span></a></div><div class="side-group"><div class="side-label">System</div><a class="side-link" href="/config/"><span class="num">01</span><span>Config</span></a><a class="side-link" href="/infrastructure/"><span class="num">02</span><span>Infrastructure</span></a></div><div class="side-foot">Local dashboard for Construct. Theme + density preferences persist.</div></aside><main class="main"><div class="page"><div class="eyebrow"><span class="dot"></span><span>overview · construct dashboard</span></div><h1 class="page-title">System state</h1><p class="page-lede">The runtime in one place. Approvals, services, model providers, knowledge corpus, and intake — each section deep-links to its dedicated surface.</p><div class="meta-strip"><span class="pill">mode: <!-- -->…</span><span>instance: <!-- -->…</span><span class="sep">·</span><span>embed: <!-- -->stopped</span></div><div class="body"><div class="hero-stats" style="margin-top:0;margin-bottom:24px"><div class="hero-stat"><span class="k">Services</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">running</span></div><div class="hero-stat"><span class="k">Approvals</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">pending</span></div><div class="hero-stat"><span class="k">Skills</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">registered</span></div><div class="hero-stat"><span class="k">Commands</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">slash</span></div></div><section class="section open"><button class="section-head" aria-expanded="true" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">01</span><span class="section-title">Pending approvals</span></span><span class="section-aside"><span>empty</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Items waiting on a human gate. High-risk mutations (work item creation, merge, doc publish, config changes) land here.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">02</span><span class="section-title">Services</span></span><span class="section-aside"><span>0/0 running</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Local processes that back the runtime: Postgres, memory bridge, OpenCode bridge, dashboard, embed daemon.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div><p style="margin-top:12px"><a class="link" href="/resources/">Open services dashboard →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">03</span><span class="section-title">Credentials</span></span><span class="section-aside"><span>0 tracked</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Provider API keys, billing posture, and 1Password references. Set them in /providers.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><p style="margin-top:12px"><a class="link" href="/providers/">Manage providers →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">04</span><span class="section-title">Quick links</span></span><span class="section-aside"></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Deep links into every dashboard surface.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div class="callout"><span class="clt-label">Where to next</span><div><p><a class="link" href="/doctor/">Doctor</a>,<!-- --> <a class="link" href="/knowledge/">Knowledge</a>,<!-- --> <a class="link" href="/intake/">Intake</a>,<!-- --> <a class="link" href="/workflow/">Workflow</a>,<!-- --> <a class="link" href="/agents/">Specialists</a>,<!-- --> <a class="link" href="/models/">Models</a>,<!-- --> <a class="link" href="/audit/">Audit</a>.</p></div></div></div></div></div></div></section></div></div><!--$--><!--/$--></main></div></div><script src="/_next/static/chunks/webpack-8e0503f8a36190b3.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[105,[\"9664\",\"static/chunks/9664-af80478aa73ba424.js\",\"8144\",\"static/chunks/8144-da56a4a291c0b21a.js\",\"7177\",\"static/chunks/app/layout-1a2d2674aa220229.js\"],\"AppShellClient\"]\n3:I[5341,[],\"\"]\n4:I[25,[],\"\"]\n5:I[1012,[],\"ClientPageRoot\"]\n6:I[9008,[\"9664\",\"static/chunks/9664-af80478aa73ba424.js\",\"8144\",\"static/chunks/8144-da56a4a291c0b21a.js\",\"8974\",\"static/chunks/app/page-2568fbdd2b0e1eca.js\"],\"default\"]\n9:I[5104,[],\"OutletBoundary\"]\nb:I[7158,[],\"AsyncMetadataOutlet\"]\nd:I[5104,[],\"ViewportBoundary\"]\nf:I[5104,[],\"MetadataBoundary\"]\n10:\"$Sreact.suspense\"\n12:I[4431,[],\"\"]\n:HL[\"/_next/static/media/22a5144ee8d83bca-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/media/bb3ef058b751a6ad-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/47a506ef57811fb9.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"qA_CTDVxjnG_0_bArvNYn\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/47a506ef57811fb9.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"data-theme\":\"dark\",\"data-density\":\"comfortable\",\"data-motion\":\"normal\",\"suppressHydrationWarning\":true,\"className\":\"__variable_629dad __variable_246ccd __variable_3c557b\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L2\",null,{\"nav\":[{\"label\":\"Overview\",\"items\":[{\"id\":\"home\",\"title\":\"Home\",\"href\":\"/\"},{\"id\":\"doctor\",\"title\":\"Doctor\",\"href\":\"/doctor\"},{\"id\":\"resources\",\"title\":\"Services\",\"href\":\"/resources\"}]},{\"label\":\"Activity\",\"items\":[{\"id\":\"audit\",\"title\":\"Audit trail\",\"href\":\"/audit\"},{\"id\":\"snapshots\",\"title\":\"Snapshots\",\"href\":\"/snapshots\"},{\"id\":\"performance\",\"title\":\"Performance\",\"href\":\"/performance\"}]},{\"label\":\"Work\",\"items\":[{\"id\":\"approvals\",\"title\":\"Approvals\",\"href\":\"/approvals\"},{\"id\":\"workflow\",\"title\":\"Workflow\",\"href\":\"/workflow\"},{\"id\":\"beads\",\"title\":\"Beads\",\"href\":\"/beads\"},{\"id\":\"artifacts\",\"title\":\"Artifacts\",\"href\":\"/artifacts\"},{\"id\":\"intake\",\"title\":\"Intake\",\"href\":\"/intake\"}]},{\"label\":\"Specialists\",\"items\":[{\"id\":\"agents\",\"title\":\"Specialists\",\"href\":\"/agents\"},{\"id\":\"skills\",\"title\":\"Skills\",\"href\":\"/skills\"},{\"id\":\"commands\",\"title\":\"Slash commands\",\"href\":\"/commands\"},{\"id\":\"hooks\",\"title\":\"Hooks\",\"href\":\"/hooks\"},{\"id\":\"plugins\",\"title\":\"Plugins\",\"href\":\"/plugins\"}]},{\"label\":\"Models \u0026 Providers\",\"items\":[{\"id\":\"models\",\"title\":\"Models\",\"href\":\"/models\"},{\"id\":\"providers\",\"title\":\"Providers\",\"href\":\"/providers\"},{\"id\":\"mcp\",\"title\":\"MCP servers\",\"href\":\"/mcp\"}]},{\"label\":\"Knowledge\",\"items\":[{\"id\":\"knowledge\",\"title\":\"Knowledge\",\"href\":\"/knowledge\"},{\"id\":\"editor\",\"title\":\"Editor\",\"href\":\"/editor\"}]},{\"label\":\"System\",\"items\":[{\"id\":\"config\",\"title\":\"Config\",\"href\":\"/config\"},{\"id\":\"infrastructure\",\"title\":\"Infrastructure\",\"href\":\"/infrastructure\"}]}],\"children\":[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L5\",null,{\"Component\":\"$6\",\"searchParams\":{},\"params\":{},\"promises\":[\"$@7\",\"$@8\"]}],null,[\"$\",\"$L9\",null,{\"children\":[\"$La\",[\"$\",\"$Lb\",null,{\"promise\":\"$@c\"}]]}]]}],{},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[null,[[\"$\",\"$Ld\",null,{\"children\":\"$Le\"}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]],[\"$\",\"$Lf\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$10\",null,{\"fallback\":null,\"children\":\"$L11\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$12\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"7:{}\n8:\"$0:f:0:1:2:children:1:props:children:0:props:params\"\n"])</script><script>self.__next_f.push([1,"e:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\na:null\n"])</script><script>self.__next_f.push([1,"c:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Construct — Dashboard\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Local operations dashboard for Construct — approvals, health, knowledge, models, providers.\"}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"11:\"$c:metadata\"\n"])</script></body></html>
|
|
1
|
+
<!DOCTYPE html><html lang="en" data-theme="dark" data-density="comfortable" data-motion="normal" class="geist_478ec610-module__Maz4kG__variable geist_d633ac5f-module__BboVrW__variable jetbrains_mono_dc177b0e-module__78ed6G__variable"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/70bc3e132a0a741e-s.p.3t6q91iet4nsy.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="preload" href="/_next/static/media/caa3a2e1cccd8315-s.p.0wgildi0cnwt9.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/chunks/07baqf-hmk6oo.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/0i88v4ql6s7aw.js"/><script src="/_next/static/chunks/26rq-xwqn81a7.js" async=""></script><script src="/_next/static/chunks/1q2ld_2skrq5q.js" async=""></script><script src="/_next/static/chunks/41yml7ep3qrnx.js" async=""></script><script src="/_next/static/chunks/turbopack-1wenwimq-yd36.js" async=""></script><script src="/_next/static/chunks/39l2haooi0nh6.js" async=""></script><script src="/_next/static/chunks/1-8s9_t85wwr4.js" async=""></script><script src="/_next/static/chunks/0cangwgy_395t.js" async=""></script><meta name="next-size-adjust" content=""/><title>Construct — Dashboard</title><meta name="description" content="Local operations dashboard for Construct — approvals, health, knowledge, models, providers."/><script src="/_next/static/chunks/0cz1d0mv5g_q7.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div class="shell"><header class="topbar"><a class="brand" href="/"><div class="mark"></div><div class="name">Construct<em>dashboard</em></div></a><div class="search-wrap"><label class="search"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"></circle><line x1="21" y1="21" x2="16.5" y2="16.5"></line></svg><input readOnly="" placeholder="Search dashboard…"/><span class="kbd">⌘K</span></label></div><div class="top-actions"><button class="icon-btn" title="Toggle theme" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"></circle><line x1="12" y1="2" x2="12" y2="5"></line><line x1="12" y1="19" x2="12" y2="22"></line><line x1="2" y1="12" x2="5" y2="12"></line><line x1="19" y1="12" x2="22" y2="12"></line><line x1="4.5" y1="4.5" x2="6.5" y2="6.5"></line><line x1="17.5" y1="17.5" x2="19.5" y2="19.5"></line><line x1="4.5" y1="19.5" x2="6.5" y2="17.5"></line><line x1="17.5" y1="6.5" x2="19.5" y2="4.5"></line></svg></button><button class="icon-btn" title="Toggle density" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="7" x2="21" y2="7"></line><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="17" x2="21" y2="17"></line></svg></button><a class="icon-btn outlined" href="https://github.com/geraldmaron/construct" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M12 .5C5.65.5.5 5.66.5 12.02c0 5.09 3.29 9.4 7.86 10.92.58.1.79-.25.79-.55v-2.13c-3.2.7-3.87-1.37-3.87-1.37-.52-1.32-1.27-1.67-1.27-1.67-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.76 2.69 1.25 3.35.96.1-.74.4-1.25.73-1.54-2.55-.29-5.24-1.28-5.24-5.7 0-1.26.45-2.29 1.18-3.1-.12-.29-.51-1.46.11-3.04 0 0 .96-.31 3.16 1.18a10.9 10.9 0 0 1 5.74 0c2.2-1.49 3.16-1.18 3.16-1.18.62 1.58.23 2.75.12 3.04.74.81 1.18 1.84 1.18 3.1 0 4.43-2.69 5.41-5.25 5.69.41.36.78 1.05.78 2.13v3.16c0 .31.21.66.79.55A11.52 11.52 0 0 0 23.5 12.02C23.5 5.66 18.35.5 12 .5Z"></path></svg> <span style="font-size:11px">repo</span></a></div><div class="progress"></div></header><div class="body-grid"><aside class="sidebar"><div class="side-group"><div class="side-label">Overview</div><a class="side-link active" href="/"><span class="num">01</span><span>Home</span></a><a class="side-link" href="/doctor/"><span class="num">02</span><span>Doctor</span></a><a class="side-link" href="/resources/"><span class="num">03</span><span>Services</span></a></div><div class="side-group"><div class="side-label">Activity</div><a class="side-link" href="/audit/"><span class="num">01</span><span>Audit trail</span></a><a class="side-link" href="/snapshots/"><span class="num">02</span><span>Snapshots</span></a><a class="side-link" href="/performance/"><span class="num">03</span><span>Performance</span></a></div><div class="side-group"><div class="side-label">Work</div><a class="side-link" href="/approvals/"><span class="num">01</span><span>Approvals</span></a><a class="side-link" href="/workflow/"><span class="num">02</span><span>Workflow</span></a><a class="side-link" href="/beads/"><span class="num">03</span><span>Beads</span></a><a class="side-link" href="/artifacts/"><span class="num">04</span><span>Artifacts</span></a><a class="side-link" href="/intake/"><span class="num">05</span><span>Intake</span></a></div><div class="side-group"><div class="side-label">Specialists</div><a class="side-link" href="/agents/"><span class="num">01</span><span>Specialists</span></a><a class="side-link" href="/skills/"><span class="num">02</span><span>Skills</span></a><a class="side-link" href="/commands/"><span class="num">03</span><span>Slash commands</span></a><a class="side-link" href="/hooks/"><span class="num">04</span><span>Hooks</span></a><a class="side-link" href="/plugins/"><span class="num">05</span><span>Plugins</span></a></div><div class="side-group"><div class="side-label">Models & Providers</div><a class="side-link" href="/models/"><span class="num">01</span><span>Models</span></a><a class="side-link" href="/providers/"><span class="num">02</span><span>Providers</span></a><a class="side-link" href="/mcp/"><span class="num">03</span><span>MCP servers</span></a></div><div class="side-group"><div class="side-label">Knowledge</div><a class="side-link" href="/knowledge/"><span class="num">01</span><span>Knowledge</span></a><a class="side-link" href="/editor/"><span class="num">02</span><span>Editor</span></a></div><div class="side-group"><div class="side-label">System</div><a class="side-link" href="/config/"><span class="num">01</span><span>Config</span></a><a class="side-link" href="/infrastructure/"><span class="num">02</span><span>Infrastructure</span></a></div><div class="side-foot">Local dashboard for Construct. Theme + density preferences persist.</div></aside><main class="main"><div class="page"><div class="eyebrow"><span class="dot"></span><span>overview · construct dashboard</span></div><h1 class="page-title">System state</h1><p class="page-lede">The runtime in one place. Approvals, services, model providers, knowledge corpus, and intake — each section deep-links to its dedicated surface.</p><div class="meta-strip"><span class="pill">mode: <!-- -->…</span><span>instance: <!-- -->…</span><span class="sep">·</span><span>embed: <!-- -->stopped</span></div><div class="body"><div class="hero-stats" style="margin-top:0;margin-bottom:24px"><div class="hero-stat"><span class="k">Services</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">running</span></div><div class="hero-stat"><span class="k">Approvals</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">pending</span></div><div class="hero-stat"><span class="k">Skills</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">registered</span></div><div class="hero-stat"><span class="k">Commands</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">slash</span></div></div><section class="section open"><button class="section-head" aria-expanded="true" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">01</span><span class="section-title">Pending approvals</span></span><span class="section-aside"><span>empty</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Items waiting on a human gate. High-risk mutations (work item creation, merge, doc publish, config changes) land here.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">02</span><span class="section-title">Services</span></span><span class="section-aside"><span>0/0 running</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Local processes that back the runtime: Postgres, memory bridge, OpenCode bridge, dashboard, embed daemon.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div><p style="margin-top:12px"><a class="link" href="/resources/">Open services dashboard →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">03</span><span class="section-title">Credentials</span></span><span class="section-aside"><span>0 tracked</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Provider API keys, billing posture, and 1Password references. Set them in /providers.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><p style="margin-top:12px"><a class="link" href="/providers/">Manage providers →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">04</span><span class="section-title">Quick links</span></span><span class="section-aside"></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Deep links into every dashboard surface.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div class="callout"><span class="clt-label">Where to next</span><div><p><a class="link" href="/doctor/">Doctor</a>,<!-- --> <a class="link" href="/knowledge/">Knowledge</a>,<!-- --> <a class="link" href="/intake/">Intake</a>,<!-- --> <a class="link" href="/workflow/">Workflow</a>,<!-- --> <a class="link" href="/agents/">Specialists</a>,<!-- --> <a class="link" href="/models/">Models</a>,<!-- --> <a class="link" href="/audit/">Audit</a>.</p></div></div></div></div></div></div></section></div></div><!--$--><!--/$--></main></div></div><script src="/_next/static/chunks/0i88v4ql6s7aw.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[95327,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"AppShellClient\"]\n3:I[39756,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"default\"]\n4:I[37457,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"default\"]\n5:I[47257,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"ClientPageRoot\"]\n6:I[62453,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\",\"/_next/static/chunks/0cangwgy_395t.js\"],\"default\"]\n9:I[97367,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"OutletBoundary\"]\na:\"$Sreact.suspense\"\nc:I[97367,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"ViewportBoundary\"]\ne:I[97367,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"MetadataBoundary\"]\n10:I[68027,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"default\",1]\n:HL[\"/_next/static/chunks/07baqf-hmk6oo.css\",\"style\"]\n:HL[\"/_next/static/media/70bc3e132a0a741e-s.p.3t6q91iet4nsy.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/media/caa3a2e1cccd8315-s.p.0wgildi0cnwt9.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",16],[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/07baqf-hmk6oo.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/39l2haooi0nh6.js\",\"async\":true,\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-1\",{\"src\":\"/_next/static/chunks/1-8s9_t85wwr4.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"data-theme\":\"dark\",\"data-density\":\"comfortable\",\"data-motion\":\"normal\",\"suppressHydrationWarning\":true,\"className\":\"geist_478ec610-module__Maz4kG__variable geist_d633ac5f-module__BboVrW__variable jetbrains_mono_dc177b0e-module__78ed6G__variable\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L2\",null,{\"nav\":[{\"label\":\"Overview\",\"items\":[{\"id\":\"home\",\"title\":\"Home\",\"href\":\"/\"},{\"id\":\"doctor\",\"title\":\"Doctor\",\"href\":\"/doctor\"},{\"id\":\"resources\",\"title\":\"Services\",\"href\":\"/resources\"}]},{\"label\":\"Activity\",\"items\":[{\"id\":\"audit\",\"title\":\"Audit trail\",\"href\":\"/audit\"},{\"id\":\"snapshots\",\"title\":\"Snapshots\",\"href\":\"/snapshots\"},{\"id\":\"performance\",\"title\":\"Performance\",\"href\":\"/performance\"}]},{\"label\":\"Work\",\"items\":[{\"id\":\"approvals\",\"title\":\"Approvals\",\"href\":\"/approvals\"},{\"id\":\"workflow\",\"title\":\"Workflow\",\"href\":\"/workflow\"},{\"id\":\"beads\",\"title\":\"Beads\",\"href\":\"/beads\"},{\"id\":\"artifacts\",\"title\":\"Artifacts\",\"href\":\"/artifacts\"},{\"id\":\"intake\",\"title\":\"Intake\",\"href\":\"/intake\"}]},{\"label\":\"Specialists\",\"items\":[{\"id\":\"agents\",\"title\":\"Specialists\",\"href\":\"/agents\"},{\"id\":\"skills\",\"title\":\"Skills\",\"href\":\"/skills\"},{\"id\":\"commands\",\"title\":\"Slash commands\",\"href\":\"/commands\"},{\"id\":\"hooks\",\"title\":\"Hooks\",\"href\":\"/hooks\"},{\"id\":\"plugins\",\"title\":\"Plugins\",\"href\":\"/plugins\"}]},{\"label\":\"Models \u0026 Providers\",\"items\":[{\"id\":\"models\",\"title\":\"Models\",\"href\":\"/models\"},{\"id\":\"providers\",\"title\":\"Providers\",\"href\":\"/providers\"},{\"id\":\"mcp\",\"title\":\"MCP servers\",\"href\":\"/mcp\"}]},{\"label\":\"Knowledge\",\"items\":[{\"id\":\"knowledge\",\"title\":\"Knowledge\",\"href\":\"/knowledge\"},{\"id\":\"editor\",\"title\":\"Editor\",\"href\":\"/editor\"}]},{\"label\":\"System\",\"items\":[{\"id\":\"config\",\"title\":\"Config\",\"href\":\"/config\"},{\"id\":\"infrastructure\",\"title\":\"Infrastructure\",\"href\":\"/infrastructure\"}]}],\"children\":[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L5\",null,{\"Component\":\"$6\",\"serverProvidedParams\":{\"searchParams\":{},\"params\":{},\"promises\":[\"$@7\",\"$@8\"]}}],[[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/0cangwgy_395t.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"$L9\",null,{\"children\":[\"$\",\"$a\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@b\"}]}]]}],{},null,false,null]},null,false,null],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$Lc\",null,{\"children\":\"$Ld\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$Le\",null,{\"children\":[\"$\",\"$a\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lf\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$10\",[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/07baqf-hmk6oo.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"piZVVlR5yulp6NxkELzTZ\"}\n"])</script><script>self.__next_f.push([1,"7:{}\n8:\"$0:f:0:1:1:children:0:props:children:0:props:serverProvidedParams:params\"\n"])</script><script>self.__next_f.push([1,"d:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"b:null\nf:[[\"$\",\"title\",\"0\",{\"children\":\"Construct — Dashboard\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Local operations dashboard for Construct — approvals, health, knowledge, models, providers.\"}]]\n"])</script></body></html>
|
package/lib/setup.mjs
CHANGED
|
@@ -794,7 +794,13 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
794
794
|
console.log(' construct install --yes');
|
|
795
795
|
}
|
|
796
796
|
|
|
797
|
+
// Install is machine setup: the global front-door agent must land on every
|
|
798
|
+
// user-scope surface (opencode/claude/codex/copilot) or a fresh machine fails
|
|
799
|
+
// cross-surface parity. Plain sync writes global hooks + project tier but not
|
|
800
|
+
// the front-door agent — `--global` is what populates it, so run both.
|
|
801
|
+
|
|
797
802
|
runConstruct(['sync']);
|
|
803
|
+
runConstruct(['sync', '--global']);
|
|
798
804
|
runConstruct(['doctor']);
|
|
799
805
|
|
|
800
806
|
// Profile the CWD so per-host skill filtering layers downstream have
|