@geraldmaron/construct 1.1.0 → 1.1.1
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 +2 -0
- package/bin/construct +81 -10
- package/lib/audit-trail.mjs +77 -10
- package/lib/cli-commands.mjs +108 -1
- package/lib/demo.mjs +245 -0
- package/lib/diagram.mjs +300 -0
- package/lib/doctor/index.mjs +1 -1
- package/lib/doctor/watchers/process-pressure.mjs +1 -1
- package/lib/doctor/watchers/service-health.mjs +1 -1
- package/lib/document-extract.mjs +48 -0
- package/lib/document-ingest.mjs +7 -2
- package/lib/embed/semantic.mjs +5 -2
- package/lib/handoffs/cleanup.mjs +1 -1
- package/lib/hooks/audit-trail.mjs +14 -28
- package/lib/model-router.mjs +52 -1
- package/lib/persona-sections.mjs +66 -0
- package/lib/prompt-composer.js +2 -1
- package/lib/server/index.mjs +1 -1
- package/lib/server/langfuse-login.mjs +3 -3
- package/lib/service-manager.mjs +1 -1
- package/lib/session-store.mjs +1 -1
- package/lib/setup.mjs +39 -2
- package/lib/storage/embeddings-local.mjs +6 -3
- package/lib/storage/file-lock.mjs +18 -11
- package/package.json +4 -2
- package/personas/construct.md +11 -11
- package/scripts/sync-specialists.mjs +149 -35
- package/skills/docs/init-project.md +1 -1
- package/templates/docs/construct_guide.md +2 -2
- package/lib/ingest/chunker.mjs +0 -94
- package/lib/ingest/pipeline.mjs +0 -53
- package/lib/ingest/store.mjs +0 -82
- package/lib/mode-commands.mjs +0 -122
- package/lib/policy/unified-gates.mjs +0 -96
- package/lib/profiles/validate-custom.mjs +0 -114
- package/lib/services/telemetry-backend.mjs +0 -177
- package/lib/storage/fusion.mjs +0 -95
package/README.md
CHANGED
|
@@ -149,6 +149,8 @@ The embed daemon writes its supervisor stdout log to `~/.cx/runtime/embed-daemon
|
|
|
149
149
|
| `construct ask` | One-shot ask against the active knowledge index |
|
|
150
150
|
| `construct bootstrap` | Import seed observation corpus into local memory store for cold-start acceleration |
|
|
151
151
|
| `construct customer` | Manage customer profiles for product intelligence |
|
|
152
|
+
| `construct demo` | Record reproducible terminal demos via VHS/asciinema (optional system binaries; ADR-0001) |
|
|
153
|
+
| `construct diagram` | Render code-driven diagrams via D2/Graphviz (optional system binaries; ADR-0001) |
|
|
152
154
|
| `construct distill` | Distill documents with query-focused chunking |
|
|
153
155
|
| `construct drop` | Ingest file from Downloads/Desktop |
|
|
154
156
|
| `construct export` | Export markdown to PDF/DOCX/HTML via Pandoc + Typst (optional system binaries; ADR-0024) |
|
package/bin/construct
CHANGED
|
@@ -1229,7 +1229,7 @@ async function resolveServiceSelection(args, selectable) {
|
|
|
1229
1229
|
return null;
|
|
1230
1230
|
}
|
|
1231
1231
|
|
|
1232
|
-
async function
|
|
1232
|
+
async function cmdDev(args = []) {
|
|
1233
1233
|
const { SELECTABLE_SERVICES } = await import('../lib/service-manager.mjs');
|
|
1234
1234
|
|
|
1235
1235
|
// --only=a,b,c picks services non-interactively; --select opens a checklist.
|
|
@@ -1365,7 +1365,7 @@ async function cmdUp(args = []) {
|
|
|
1365
1365
|
opLog.close(failed.length ? 'degraded' : 'ok', { failed: failed.map((r) => r.name) });
|
|
1366
1366
|
}
|
|
1367
1367
|
|
|
1368
|
-
async function
|
|
1368
|
+
async function cmdStop() {
|
|
1369
1369
|
const { stopServices } = await import('../lib/service-manager.mjs');
|
|
1370
1370
|
const result = await stopServices({ homeDir: HOME, rootDir: ROOT_DIR });
|
|
1371
1371
|
for (const svc of result.results) {
|
|
@@ -1384,7 +1384,7 @@ async function cmdDown() {
|
|
|
1384
1384
|
} catch { /* best effort */ }
|
|
1385
1385
|
}
|
|
1386
1386
|
|
|
1387
|
-
async function
|
|
1387
|
+
async function cmdDashboard() {
|
|
1388
1388
|
if (restArgsCache.includes('--token')) {
|
|
1389
1389
|
const { generateToken, setDashboardToken, getDashboardToken } = await import('../lib/server/auth.mjs');
|
|
1390
1390
|
const existing = getDashboardToken();
|
|
@@ -1951,7 +1951,7 @@ async function cmdHosts() {
|
|
|
1951
1951
|
printHostCapabilities();
|
|
1952
1952
|
}
|
|
1953
1953
|
}
|
|
1954
|
-
async function
|
|
1954
|
+
async function cmdInstall(args) {
|
|
1955
1955
|
const { startOpLog } = await import('../lib/op-log.mjs');
|
|
1956
1956
|
const opLog = startOpLog('install', { homeDir: HOME });
|
|
1957
1957
|
opLog.event('args', { args });
|
|
@@ -2402,7 +2402,7 @@ async function cmdIntakeProcess(args, cwd) {
|
|
|
2402
2402
|
errorln('Choose one:');
|
|
2403
2403
|
errorln(' construct intake process --wait Block until the holder finishes (default 30s).');
|
|
2404
2404
|
errorln(' construct intake process --wait=<sec> Block for a custom window.');
|
|
2405
|
-
errorln(' construct
|
|
2405
|
+
errorln(' construct stop Stop background services, then retry.');
|
|
2406
2406
|
process.exit(2);
|
|
2407
2407
|
}
|
|
2408
2408
|
throw err;
|
|
@@ -5209,11 +5209,11 @@ async function readStdin() {
|
|
|
5209
5209
|
|
|
5210
5210
|
const handlers = new Map([
|
|
5211
5211
|
// Core
|
|
5212
|
-
['dev',
|
|
5213
|
-
['dashboard',
|
|
5214
|
-
['stop',
|
|
5212
|
+
['dev', cmdDev],
|
|
5213
|
+
['dashboard', cmdDashboard],
|
|
5214
|
+
['stop', cmdStop],
|
|
5215
5215
|
['status', cmdStatus],
|
|
5216
|
-
['install',
|
|
5216
|
+
['install', cmdInstall],
|
|
5217
5217
|
['config', cmdConfig],
|
|
5218
5218
|
['intake', cmdIntake],
|
|
5219
5219
|
['recommendations', cmdRecommendations],
|
|
@@ -5838,6 +5838,14 @@ const handlers = new Map([
|
|
|
5838
5838
|
const { runWireframeCli } = await import('../lib/wireframe.mjs');
|
|
5839
5839
|
return runWireframeCli(args);
|
|
5840
5840
|
}],
|
|
5841
|
+
['diagram', async (args) => {
|
|
5842
|
+
const { runDiagramCli } = await import('../lib/diagram.mjs');
|
|
5843
|
+
return runDiagramCli(args);
|
|
5844
|
+
}],
|
|
5845
|
+
['demo', async (args) => {
|
|
5846
|
+
const { runDemoCli } = await import('../lib/demo.mjs');
|
|
5847
|
+
return runDemoCli(args);
|
|
5848
|
+
}],
|
|
5841
5849
|
['skills', async (args) => {
|
|
5842
5850
|
const sub = args[0];
|
|
5843
5851
|
if (sub === 'scope' || !sub) {
|
|
@@ -6355,6 +6363,67 @@ if (command === '--version' || command === '-V') {
|
|
|
6355
6363
|
process.exit(0);
|
|
6356
6364
|
}
|
|
6357
6365
|
|
|
6366
|
+
// Unknown-flag guidance for commands that fully declare their flags (strictFlags
|
|
6367
|
+
// in the spec). A stray or typo'd flag on these is a user error, not a no-op:
|
|
6368
|
+
// reject it with the nearest declared flag and the command's help, matching how
|
|
6369
|
+
// install (lib/setup.mjs KNOWN_FLAGS) already behaves, so no flag is silently
|
|
6370
|
+
// swallowed. Opt-in keeps commands that accept undeclared flags unaffected.
|
|
6371
|
+
|
|
6372
|
+
function validateKnownFlags(name, argv) {
|
|
6373
|
+
const spec = CLI_COMMANDS.find((c) => c.name === name);
|
|
6374
|
+
if (!spec || !spec.strictFlags) return;
|
|
6375
|
+
|
|
6376
|
+
const exact = new Set(['--help', '-h']);
|
|
6377
|
+
const prefixes = [];
|
|
6378
|
+
for (const opt of spec.options || []) {
|
|
6379
|
+
for (const part of String(opt.flag).split(',')) {
|
|
6380
|
+
const token = part.trim().split('=')[0].split(/\s/)[0];
|
|
6381
|
+
if (!token.startsWith('-')) continue;
|
|
6382
|
+
if (/-<[^>]+>$/.test(token)) prefixes.push(token.replace(/<[^>]+>$/, ''));
|
|
6383
|
+
else exact.add(token);
|
|
6384
|
+
}
|
|
6385
|
+
}
|
|
6386
|
+
|
|
6387
|
+
const declared = [...exact].filter((f) => f.startsWith('--'));
|
|
6388
|
+
const dist = (a, b) => {
|
|
6389
|
+
const row = Array(b.length + 1).fill(0).map((_, i) => i);
|
|
6390
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
6391
|
+
let prev = i;
|
|
6392
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
6393
|
+
const cur = a[i - 1] === b[j - 1] ? row[j - 1] : 1 + Math.min(row[j - 1], row[j], prev);
|
|
6394
|
+
row[j - 1] = prev;
|
|
6395
|
+
prev = cur;
|
|
6396
|
+
}
|
|
6397
|
+
row[b.length] = prev;
|
|
6398
|
+
}
|
|
6399
|
+
return row[b.length];
|
|
6400
|
+
};
|
|
6401
|
+
|
|
6402
|
+
const unknown = [];
|
|
6403
|
+
for (const tok of argv) {
|
|
6404
|
+
if (tok === '--') break;
|
|
6405
|
+
if (!/^--?[A-Za-z]/.test(tok)) continue;
|
|
6406
|
+
const bare = tok.split('=')[0];
|
|
6407
|
+
if (exact.has(bare) || prefixes.some((p) => bare.startsWith(p))) continue;
|
|
6408
|
+
unknown.push(bare);
|
|
6409
|
+
}
|
|
6410
|
+
if (unknown.length === 0) return;
|
|
6411
|
+
|
|
6412
|
+
for (const flag of unknown) {
|
|
6413
|
+
errorln(`Unknown flag: ${flag}`);
|
|
6414
|
+
let best = 4;
|
|
6415
|
+
let suggestion = null;
|
|
6416
|
+
for (const cand of declared) {
|
|
6417
|
+
const d = dist(flag, cand);
|
|
6418
|
+
if (d < best) { best = d; suggestion = cand; }
|
|
6419
|
+
}
|
|
6420
|
+
if (suggestion) errorln(`Did you mean: ${suggestion}?`);
|
|
6421
|
+
}
|
|
6422
|
+
println('');
|
|
6423
|
+
println(`${COLORS.dim}Run 'construct ${name} --help' for valid options${COLORS.reset}`);
|
|
6424
|
+
process.exit(1);
|
|
6425
|
+
}
|
|
6426
|
+
|
|
6358
6427
|
const handler = handlers.get(command);
|
|
6359
6428
|
if (!handler) {
|
|
6360
6429
|
errorln(`Unknown command: ${command}`);
|
|
@@ -6433,7 +6502,9 @@ if (command !== 'hook') {
|
|
|
6433
6502
|
|
|
6434
6503
|
const probe = await maybeFirstInvocationProbe({ command, homeDir: HOME, env: process.env });
|
|
6435
6504
|
if (probe?.runSetup) {
|
|
6436
|
-
await
|
|
6505
|
+
await cmdInstall([]);
|
|
6437
6506
|
}
|
|
6438
6507
|
|
|
6508
|
+
validateKnownFlags(command, rest);
|
|
6509
|
+
|
|
6439
6510
|
await handler(rest);
|
package/lib/audit-trail.mjs
CHANGED
|
@@ -1,22 +1,68 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/audit-trail.mjs —
|
|
3
|
-
* lib/hooks/audit-trail.mjs and
|
|
2
|
+
* lib/audit-trail.mjs — owns the append-only audit log written by
|
|
3
|
+
* lib/hooks/audit-trail.mjs and rendered for `construct audit trail`.
|
|
4
4
|
*
|
|
5
5
|
* The log is JSONL at ~/.cx/audit-trail.jsonl. Each line carries a
|
|
6
6
|
* prev_line_hash pointing at the SHA-256 of the previous line — tampering
|
|
7
7
|
* (reorder, delete, edit) breaks the chain and is surfaced by --verify.
|
|
8
|
+
*
|
|
9
|
+
* A serial hash chain cannot survive concurrent appends: a read-prev-hash +
|
|
10
|
+
* append done outside a critical section lets two processes (hook, daemon,
|
|
11
|
+
* parallel tool calls in one session) both chain off the same predecessor, so
|
|
12
|
+
* the second link points at a stale tail. appendAuditRecord
|
|
13
|
+
* closes that window by computing prev_line_hash and appending inside a single
|
|
14
|
+
* cross-process file lock, making every writer effectively serialized.
|
|
15
|
+
*
|
|
16
|
+
* Corrupted links cannot be re-derived, so a `chain_reset` boundary line seals
|
|
17
|
+
* the damaged prefix as immutable legacy and re-bases the live chain;
|
|
18
|
+
* verifyChain validates only from the last boundary forward. See
|
|
19
|
+
* docs/concepts/observability.mdx.
|
|
8
20
|
*/
|
|
9
|
-
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
21
|
+
import { readFileSync, existsSync, statSync, mkdirSync } from 'node:fs';
|
|
10
22
|
import { createHash } from 'node:crypto';
|
|
11
|
-
import { join } from 'node:path';
|
|
23
|
+
import { join, dirname } from 'node:path';
|
|
12
24
|
import { homedir } from 'node:os';
|
|
13
25
|
|
|
26
|
+
import { appendBounded, readLastLineAcrossSegments } from './logging/rotate.mjs';
|
|
27
|
+
import { withFileLockSync } from './storage/file-lock.mjs';
|
|
28
|
+
|
|
14
29
|
const AUDIT_FILE = join(homedir(), '.cx', 'audit-trail.jsonl');
|
|
15
30
|
|
|
16
31
|
function sha256(input) {
|
|
17
32
|
return createHash('sha256').update(input).digest('hex');
|
|
18
33
|
}
|
|
19
34
|
|
|
35
|
+
// prev_line_hash and the append must be one atomic step or concurrent writers
|
|
36
|
+
// race; the file lock serializes them across processes.
|
|
37
|
+
|
|
38
|
+
export function appendAuditRecord(partial, { file = AUDIT_FILE } = {}) {
|
|
39
|
+
return withFileLockSync(file, () => {
|
|
40
|
+
let prev = null;
|
|
41
|
+
try {
|
|
42
|
+
const last = readLastLineAcrossSegments(file);
|
|
43
|
+
if (last !== null) prev = sha256(last);
|
|
44
|
+
} catch { /* first record, or unreadable tail */ }
|
|
45
|
+
const record = { ...partial, prev_line_hash: prev };
|
|
46
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
47
|
+
appendBounded('audit-trail', file, JSON.stringify(record) + '\n');
|
|
48
|
+
return record;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// A reset boundary seals the records it follows as legacy excluded from
|
|
53
|
+
// verification and re-bases the live chain from this line.
|
|
54
|
+
|
|
55
|
+
export function writeChainReset({ file = AUDIT_FILE, reason = 'manual reset' } = {}) {
|
|
56
|
+
return appendAuditRecord({
|
|
57
|
+
ts: new Date().toISOString(),
|
|
58
|
+
chain_reset: true,
|
|
59
|
+
reason,
|
|
60
|
+
agent: 'construct',
|
|
61
|
+
tool: null,
|
|
62
|
+
target: null,
|
|
63
|
+
}, { file });
|
|
64
|
+
}
|
|
65
|
+
|
|
20
66
|
export function readAuditTrail({ limit = 50, since = null, agent = null, tool = null } = {}) {
|
|
21
67
|
if (!existsSync(AUDIT_FILE)) return [];
|
|
22
68
|
const lines = readFileSync(AUDIT_FILE, 'utf8').split('\n').filter(Boolean);
|
|
@@ -34,19 +80,29 @@ export function readAuditTrail({ limit = 50, since = null, agent = null, tool =
|
|
|
34
80
|
return filtered.slice(-limit);
|
|
35
81
|
}
|
|
36
82
|
|
|
37
|
-
export function verifyChain() {
|
|
38
|
-
if (!existsSync(
|
|
39
|
-
const lines = readFileSync(
|
|
83
|
+
export function verifyChain(file = AUDIT_FILE) {
|
|
84
|
+
if (!existsSync(file)) return { ok: true, verified: 0, broken: [], legacy: 0 };
|
|
85
|
+
const lines = readFileSync(file, 'utf8').split('\n').filter(Boolean);
|
|
86
|
+
|
|
87
|
+
// Validation starts at the last reset boundary; the prefix it seals is legacy
|
|
88
|
+
// and exempt, so concurrency damage predating the migration can't fail the chain.
|
|
89
|
+
|
|
90
|
+
let start = 0;
|
|
91
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
92
|
+
try { if (JSON.parse(lines[i]).chain_reset) start = i; } catch { /* legacy malformed */ }
|
|
93
|
+
}
|
|
94
|
+
|
|
40
95
|
const broken = [];
|
|
41
96
|
let prevHash = null;
|
|
42
|
-
for (let i =
|
|
97
|
+
for (let i = start; i < lines.length; i += 1) {
|
|
43
98
|
const line = lines[i];
|
|
44
99
|
let row;
|
|
45
100
|
try { row = JSON.parse(line); } catch {
|
|
46
101
|
broken.push({ line: i + 1, reason: 'malformed JSON' });
|
|
102
|
+
prevHash = sha256(line);
|
|
47
103
|
continue;
|
|
48
104
|
}
|
|
49
|
-
if (i >
|
|
105
|
+
if (i > start && !row.chain_reset && row.prev_line_hash !== prevHash) {
|
|
50
106
|
broken.push({
|
|
51
107
|
line: i + 1,
|
|
52
108
|
expected: prevHash,
|
|
@@ -56,7 +112,7 @@ export function verifyChain() {
|
|
|
56
112
|
}
|
|
57
113
|
prevHash = sha256(line);
|
|
58
114
|
}
|
|
59
|
-
return { ok: broken.length === 0, verified: lines.length, broken };
|
|
115
|
+
return { ok: broken.length === 0, verified: lines.length - start, broken, legacy: start };
|
|
60
116
|
}
|
|
61
117
|
|
|
62
118
|
function formatRow(row) {
|
|
@@ -74,6 +130,9 @@ export async function runAuditTrailCli(args = []) {
|
|
|
74
130
|
for (let i = 0; i < args.length; i += 1) {
|
|
75
131
|
const arg = args[i];
|
|
76
132
|
if (arg === '--verify') flags.add('verify');
|
|
133
|
+
else if (arg === '--reset') flags.add('reset');
|
|
134
|
+
else if (arg.startsWith('--reason=')) options.reason = arg.slice(9);
|
|
135
|
+
else if (arg === '--reason') options.reason = args[++i];
|
|
77
136
|
else if (arg === '--json') flags.add('json');
|
|
78
137
|
else if (arg === '--all') flags.add('all');
|
|
79
138
|
else if (arg.startsWith('--limit=')) options.limit = Number(arg.slice(8));
|
|
@@ -97,6 +156,8 @@ Options:
|
|
|
97
156
|
--agent <name> Filter to a specific agent (e.g. cx-engineer)
|
|
98
157
|
--tool <name> Filter to a specific tool (Edit, Write, MultiEdit, NotebookEdit, Bash)
|
|
99
158
|
--verify Verify the tamper-evidence chain and exit
|
|
159
|
+
--reset Seal the current chain as legacy and re-base a fresh chain
|
|
160
|
+
--reason <text> Reason recorded on the --reset boundary
|
|
100
161
|
--json Emit raw JSONL lines instead of formatted output
|
|
101
162
|
-h, --help Show this message
|
|
102
163
|
|
|
@@ -105,6 +166,12 @@ Log location: ${AUDIT_FILE}
|
|
|
105
166
|
return;
|
|
106
167
|
}
|
|
107
168
|
|
|
169
|
+
if (flags.has('reset')) {
|
|
170
|
+
writeChainReset({ reason: options.reason || 'manual reset' });
|
|
171
|
+
console.log(`Audit chain re-based at ${AUDIT_FILE}. Prior records sealed as legacy; verification resumes from the new boundary.`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
108
175
|
if (flags.has('verify')) {
|
|
109
176
|
const result = verifyChain();
|
|
110
177
|
if (result.ok) {
|
package/lib/cli-commands.mjs
CHANGED
|
@@ -23,6 +23,10 @@ export const CLI_COMMANDS = [
|
|
|
23
23
|
core: true,
|
|
24
24
|
description: 'Start services for development',
|
|
25
25
|
usage: 'construct dev [--select] [--only=postgres,dashboard,...]',
|
|
26
|
+
examples: [
|
|
27
|
+
{ cmd: 'construct dev', desc: 'Start the default service set' },
|
|
28
|
+
{ cmd: 'construct dev --only=dashboard', desc: 'Start just the dashboard' },
|
|
29
|
+
],
|
|
26
30
|
options: [
|
|
27
31
|
{ flag: '--select', desc: 'Pick which services to start from an interactive checklist' },
|
|
28
32
|
{ flag: '--only=<a,b,c>', desc: 'Start only the named services (postgres, dashboard, telemetry, memory, opencode)' },
|
|
@@ -35,6 +39,10 @@ export const CLI_COMMANDS = [
|
|
|
35
39
|
core: true,
|
|
36
40
|
description: 'Start the local dashboard/orchestration daemon (or --token to mint a dashboard token)',
|
|
37
41
|
usage: 'construct dashboard [--token]',
|
|
42
|
+
examples: [
|
|
43
|
+
{ cmd: 'construct dashboard', desc: 'Launch the local dashboard daemon' },
|
|
44
|
+
{ cmd: 'construct dashboard --token', desc: 'Mint a dashboard access token' },
|
|
45
|
+
],
|
|
38
46
|
},
|
|
39
47
|
{
|
|
40
48
|
name: 'stop',
|
|
@@ -43,6 +51,9 @@ export const CLI_COMMANDS = [
|
|
|
43
51
|
core: true,
|
|
44
52
|
description: 'Stop all running services',
|
|
45
53
|
usage: 'construct stop',
|
|
54
|
+
examples: [
|
|
55
|
+
{ cmd: 'construct stop', desc: 'Stop every running service' },
|
|
56
|
+
],
|
|
46
57
|
},
|
|
47
58
|
{
|
|
48
59
|
name: 'status',
|
|
@@ -51,6 +62,11 @@ export const CLI_COMMANDS = [
|
|
|
51
62
|
core: true,
|
|
52
63
|
description: 'Show system health and credentials',
|
|
53
64
|
usage: 'construct status',
|
|
65
|
+
strictFlags: true,
|
|
66
|
+
examples: [
|
|
67
|
+
{ cmd: 'construct status', desc: 'Human-readable health summary' },
|
|
68
|
+
{ cmd: 'construct status --json', desc: 'Full payload for scripting' },
|
|
69
|
+
],
|
|
54
70
|
options: [
|
|
55
71
|
{ flag: '--json', desc: 'Output full status payload as JSON' },
|
|
56
72
|
],
|
|
@@ -61,10 +77,15 @@ export const CLI_COMMANDS = [
|
|
|
61
77
|
category: 'Core',
|
|
62
78
|
core: true,
|
|
63
79
|
description: 'Machine setup (scoped per ADR-0029): --scope=project|user|both, default project',
|
|
64
|
-
usage: 'construct install [--scope=project|user|both] [--yes] [--no-docker] [--no-launch-agent] [--reconfigure] [--with-docling]',
|
|
80
|
+
usage: 'construct install [--scope=project|user|both] [--yes] [--dry-run] [--no-docker] [--no-launch-agent] [--reconfigure] [--with-docling]',
|
|
81
|
+
examples: [
|
|
82
|
+
{ cmd: 'construct install --dry-run', desc: 'Preview the plan before writing anything' },
|
|
83
|
+
{ cmd: 'construct install --scope=user --yes', desc: 'Non-interactive user-scope install' },
|
|
84
|
+
],
|
|
65
85
|
options: [
|
|
66
86
|
{ flag: '--scope=<s>', desc: 'project (default, no-op + guidance) | user (writes ~/.construct/, MCP, ~/.claude/* via consent) | both' },
|
|
67
87
|
{ flag: '--yes', desc: 'Apply defaults without prompts (only meaningful with --scope=user|both)' },
|
|
88
|
+
{ flag: '--dry-run', desc: 'Preview the install plan (scopes, files, services) without writing anything' },
|
|
68
89
|
{ flag: '--no-docker', desc: 'Skip Docker-based service setup (local Postgres)' },
|
|
69
90
|
{ flag: '--no-launch-agent', desc: 'Skip background macOS LaunchAgent registration' },
|
|
70
91
|
{ flag: '--reconfigure', desc: 'Re-prompt for service consent, ignoring cached answers' },
|
|
@@ -78,6 +99,10 @@ export const CLI_COMMANDS = [
|
|
|
78
99
|
core: true,
|
|
79
100
|
description: 'Project setup (once per repo): scaffold .cx/, AGENTS.md, plan.md, adapters',
|
|
80
101
|
usage: 'construct init [path] [options]',
|
|
102
|
+
examples: [
|
|
103
|
+
{ cmd: 'construct init', desc: 'Scaffold the current repo interactively' },
|
|
104
|
+
{ cmd: 'construct init --yes --with-adrs', desc: 'Non-interactive init with the ADR doc lane' },
|
|
105
|
+
],
|
|
81
106
|
options: [
|
|
82
107
|
{ flag: '--yes', desc: 'Accept all defaults (non-interactive)' },
|
|
83
108
|
{ flag: '--no-start', desc: 'Do not start services after init' },
|
|
@@ -103,6 +128,10 @@ export const CLI_COMMANDS = [
|
|
|
103
128
|
core: true,
|
|
104
129
|
description: 'Sync agent adapters to AI tools',
|
|
105
130
|
usage: 'construct sync [--project] [--dry-run] [--no-docs] [--compress-personas]',
|
|
131
|
+
examples: [
|
|
132
|
+
{ cmd: 'construct sync', desc: 'Sync adapters to detected AI tools' },
|
|
133
|
+
{ cmd: 'construct sync --dry-run', desc: 'Preview adapter changes without writing' },
|
|
134
|
+
],
|
|
106
135
|
options: [
|
|
107
136
|
{ flag: '--project', desc: 'Write project-local Claude adapters into the current repo only' },
|
|
108
137
|
{ flag: '--dry-run', desc: 'Preview adapter changes without writing files' },
|
|
@@ -117,6 +146,10 @@ export const CLI_COMMANDS = [
|
|
|
117
146
|
core: true,
|
|
118
147
|
description: 'View and process the active profile\'s intake queue (queue label varies by profile)',
|
|
119
148
|
usage: 'construct intake list|show|done|skip|reopen|integrate|classify',
|
|
149
|
+
examples: [
|
|
150
|
+
{ cmd: 'construct intake list', desc: 'See pending packets' },
|
|
151
|
+
{ cmd: 'construct intake done <id> --output=path', desc: 'Mark processed and stamp the artifact' },
|
|
152
|
+
],
|
|
120
153
|
subcommands: [
|
|
121
154
|
{ name: 'list', desc: 'List pending packets' },
|
|
122
155
|
{ name: 'show <id>', desc: 'Show one packet (triage, related docs, excerpt, tag suggestions)' },
|
|
@@ -134,6 +167,10 @@ export const CLI_COMMANDS = [
|
|
|
134
167
|
core: true,
|
|
135
168
|
description: 'View and manage artifact recommendations',
|
|
136
169
|
usage: 'construct recommendations list|show|dismiss|stats',
|
|
170
|
+
examples: [
|
|
171
|
+
{ cmd: 'construct recommendations list', desc: 'See open recommendations' },
|
|
172
|
+
{ cmd: 'construct recommendations dismiss <id>', desc: 'Dismiss one by id' },
|
|
173
|
+
],
|
|
137
174
|
},
|
|
138
175
|
{
|
|
139
176
|
name: 'integrations',
|
|
@@ -180,6 +217,10 @@ export const CLI_COMMANDS = [
|
|
|
180
217
|
core: true,
|
|
181
218
|
description: 'Isolated tmpdir-based environment for QA / specialist dry-runs',
|
|
182
219
|
usage: 'construct sandbox create|list|delete|prune [--profile=<id>]',
|
|
220
|
+
examples: [
|
|
221
|
+
{ cmd: 'construct sandbox create --profile=<id>', desc: 'Spin up an isolated sandbox' },
|
|
222
|
+
{ cmd: 'construct sandbox prune --days=7', desc: 'Remove sandboxes older than a week' },
|
|
223
|
+
],
|
|
183
224
|
subcommands: [
|
|
184
225
|
{ name: 'create [--profile=<id>]', desc: 'Create a new sandbox under ~/.cx/sandboxes/' },
|
|
185
226
|
{ name: 'list', desc: 'List existing sandboxes, newest first' },
|
|
@@ -194,6 +235,10 @@ export const CLI_COMMANDS = [
|
|
|
194
235
|
core: true,
|
|
195
236
|
description: 'Manage the active org profile and its lifecycle (draft, promote, archive, health)',
|
|
196
237
|
usage: 'construct profile show|list|set|create|drafts|archive|health',
|
|
238
|
+
examples: [
|
|
239
|
+
{ cmd: 'construct profile list', desc: 'List curated profiles' },
|
|
240
|
+
{ cmd: 'construct profile set <id>', desc: 'Switch the active profile' },
|
|
241
|
+
],
|
|
197
242
|
subcommands: [
|
|
198
243
|
{ name: 'show', desc: 'Show the active profile' },
|
|
199
244
|
{ name: 'list', desc: 'List curated profiles' },
|
|
@@ -225,6 +270,10 @@ export const CLI_COMMANDS = [
|
|
|
225
270
|
core: true,
|
|
226
271
|
description: 'Documentation commands',
|
|
227
272
|
usage: 'construct docs check|verify|update',
|
|
273
|
+
examples: [
|
|
274
|
+
{ cmd: 'construct docs verify', desc: 'Validate the docs tree' },
|
|
275
|
+
{ cmd: 'construct docs update', desc: 'Regenerate AUTO-managed regions' },
|
|
276
|
+
],
|
|
228
277
|
subcommands: [
|
|
229
278
|
{ name: 'check', desc: 'Check for missing how-to guides' },
|
|
230
279
|
{ name: 'verify', desc: 'Validate documentation quality' },
|
|
@@ -238,6 +287,10 @@ export const CLI_COMMANDS = [
|
|
|
238
287
|
core: true,
|
|
239
288
|
description: 'Check installation health',
|
|
240
289
|
usage: 'construct doctor [<status|logs|tick|report|consistency|watch|stop|credentials>] [--fix-legacy-agents]',
|
|
290
|
+
examples: [
|
|
291
|
+
{ cmd: 'construct doctor', desc: 'Run all health checks once' },
|
|
292
|
+
{ cmd: 'construct doctor report', desc: 'Print the latest health report' },
|
|
293
|
+
],
|
|
241
294
|
subcommands: [
|
|
242
295
|
{ name: 'status', desc: 'Doctor daemon status' },
|
|
243
296
|
{ name: 'logs', desc: 'Tail doctor daemon logs' },
|
|
@@ -488,6 +541,7 @@ export const CLI_COMMANDS = [
|
|
|
488
541
|
core: false,
|
|
489
542
|
description: 'Release dev-agent memory pressure by cleaning stale helper and bridge processes',
|
|
490
543
|
usage: 'construct cleanup [--dry-run] [--quiet] [--pressure-release] [--pressure-only] [--disk-only]',
|
|
544
|
+
strictFlags: true,
|
|
491
545
|
options: [
|
|
492
546
|
{ flag: '--dry-run', desc: 'Show what would be cleaned without changing anything' },
|
|
493
547
|
{ flag: '--quiet', desc: 'Minimal output' },
|
|
@@ -560,6 +614,36 @@ export const CLI_COMMANDS = [
|
|
|
560
614
|
description: 'Generate wireframes from description',
|
|
561
615
|
usage: 'construct wireframe <description>',
|
|
562
616
|
},
|
|
617
|
+
{
|
|
618
|
+
name: 'diagram',
|
|
619
|
+
emoji: '📊',
|
|
620
|
+
category: 'Work',
|
|
621
|
+
core: false,
|
|
622
|
+
description: 'Render code-driven diagrams via D2/Graphviz (optional system binaries; ADR-0001)',
|
|
623
|
+
usage: 'construct diagram <description> [--type=architecture|flow|sequence|state|er|class] [--format=svg|png] [--theme=<name>] [--out=<path>] [--source-only]',
|
|
624
|
+
strictFlags: true,
|
|
625
|
+
options: [
|
|
626
|
+
{ flag: '--type=<t>', desc: 'architecture (default) | flow | sequence | state | er | class' },
|
|
627
|
+
{ flag: '--format=<f>', desc: 'svg (default) | png' },
|
|
628
|
+
{ flag: '--theme=<name>', desc: 'D2 theme name (e.g. neutral, sketch, cool-classics)' },
|
|
629
|
+
{ flag: '--out=<path>', desc: 'Output path (default: .cx/diagrams/<slug>-<ts>.<ext>)' },
|
|
630
|
+
{ flag: '--source-only', desc: 'Always write the source file; skip rendering' },
|
|
631
|
+
],
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
name: 'demo',
|
|
635
|
+
emoji: '🎬',
|
|
636
|
+
category: 'Work',
|
|
637
|
+
core: false,
|
|
638
|
+
description: 'Record reproducible terminal demos via VHS/asciinema (optional system binaries; ADR-0001)',
|
|
639
|
+
usage: 'construct demo <name> [--format=gif|mp4|webm] [--out=<path>] [--source-only]',
|
|
640
|
+
strictFlags: true,
|
|
641
|
+
options: [
|
|
642
|
+
{ flag: '--format=<f>', desc: 'gif (default) | mp4 | webm (VHS only)' },
|
|
643
|
+
{ flag: '--out=<path>', desc: 'Output path (default: .cx/demos/<name>-<ts>.<ext>)' },
|
|
644
|
+
{ flag: '--source-only', desc: 'Always write the .tape source; skip recording' },
|
|
645
|
+
],
|
|
646
|
+
},
|
|
563
647
|
{
|
|
564
648
|
name: 'ollama',
|
|
565
649
|
emoji: '🦙',
|
|
@@ -1035,5 +1119,28 @@ export function formatCommandHelp(name, { colors = false } = {}) {
|
|
|
1035
1119
|
}
|
|
1036
1120
|
lines.push('');
|
|
1037
1121
|
}
|
|
1122
|
+
|
|
1123
|
+
if (spec.examples && spec.examples.length > 0) {
|
|
1124
|
+
lines.push(`${c.bold}Examples${c.reset}`);
|
|
1125
|
+
for (const ex of spec.examples) {
|
|
1126
|
+
lines.push(` ${ex.cmd}`);
|
|
1127
|
+
if (ex.desc) lines.push(` ${c.dim}${ex.desc}${c.reset}`);
|
|
1128
|
+
}
|
|
1129
|
+
lines.push('');
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// Every help block closes with the obvious next action: an explicit `next`,
|
|
1133
|
+
// else "run a subcommand" for grouped commands, plus a See also pointer to the
|
|
1134
|
+
// full reference — so no surface is a dead end.
|
|
1135
|
+
|
|
1136
|
+
const firstSub = spec.subcommands?.[0]?.name.split(/\s+/)[0];
|
|
1137
|
+
const next = spec.next
|
|
1138
|
+
|| (firstSub ? `Run \`construct ${spec.name} ${firstSub}\` to start; add --help to any subcommand for details.` : null);
|
|
1139
|
+
if (next) {
|
|
1140
|
+
lines.push(`${c.dim}Next:${c.reset} ${next}`);
|
|
1141
|
+
lines.push('');
|
|
1142
|
+
}
|
|
1143
|
+
lines.push(`${c.dim}See also:${c.reset} run \`construct --help\` for all commands; full reference in docs/reference/cli/.`);
|
|
1144
|
+
lines.push('');
|
|
1038
1145
|
return lines.join('\n');
|
|
1039
1146
|
}
|