@geraldmaron/construct 1.0.21 → 1.0.23
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 +23 -5
- package/bin/construct +191 -13
- package/bin/construct-postinstall.mjs +25 -15
- package/lib/acp/server.mjs +113 -0
- package/lib/agent-instructions/inject.mjs +94 -0
- package/lib/auto-docs.mjs +10 -2
- package/lib/cli-commands.mjs +43 -15
- package/lib/comment-lint.mjs +115 -0
- package/lib/completions.mjs +7 -1
- package/lib/config/schema.mjs +4 -0
- package/lib/decisions/enforced-baseline.json +2 -0
- package/lib/docs-verify.mjs +15 -18
- package/lib/doctor/cli.mjs +8 -1
- package/lib/document-export.mjs +124 -0
- package/lib/embed/daemon.mjs +1 -1
- package/lib/embed/inbox.mjs +25 -7
- package/lib/features.mjs +11 -11
- package/lib/git-hooks-path.mjs +61 -0
- package/lib/home-namespace.mjs +60 -0
- package/lib/hooks/ci-status-check.mjs +62 -40
- package/lib/hooks/orchestration-dispatch-guard.mjs +153 -0
- package/lib/hooks/pre-push-gate.mjs +15 -6
- package/lib/hooks/stop-notify.mjs +32 -17
- package/lib/hooks/stop-typecheck.mjs +7 -2
- package/lib/host-capabilities.mjs +24 -8
- package/lib/host-disposition.mjs +76 -0
- package/lib/ingest/provider-extract.mjs +1 -1
- package/lib/init-docs.mjs +1 -1
- package/lib/init-unified.mjs +320 -219
- package/lib/init-update.mjs +4 -84
- package/lib/init.mjs +9 -25
- package/lib/install/stage-project.mjs +8 -2
- package/lib/intent-classifier.mjs +1 -1
- package/lib/knowledge/search.mjs +52 -3
- package/lib/mcp/server.mjs +57 -14
- package/lib/mcp/tools/memory.mjs +2 -2
- package/lib/mcp/tools/orchestration-run.mjs +125 -0
- package/lib/model-registry.mjs +40 -33
- package/lib/observation-store.mjs +6 -2
- package/lib/opencode-config.mjs +1 -1
- package/lib/orchestration/events.mjs +66 -0
- package/lib/orchestration/runtime.mjs +70 -9
- package/lib/orchestration/worker.mjs +69 -21
- package/lib/orchestration-policy.mjs +27 -3
- package/lib/parity.mjs +46 -24
- package/lib/policy/unified-gates.mjs +96 -0
- package/lib/project-init-shared.mjs +0 -173
- package/lib/reconcile/adapter-prune.mjs +105 -0
- package/lib/reconcile/agent-instructions-rewrap.mjs +98 -0
- package/lib/reconcile/gitignore-coverage.mjs +88 -0
- package/lib/reconcile/index.mjs +171 -0
- package/lib/reconcile/legacy-doctrine-strip.mjs +139 -0
- package/lib/reconcile/legacy-guide-decommit.mjs +67 -0
- package/lib/reconcile/legacy-skills-cleanup.mjs +200 -0
- package/lib/reconcile/mcp-entry-reconcile.mjs +142 -0
- package/lib/reconcile/postgres-namespace.mjs +102 -0
- package/lib/runtime/uv-bootstrap.mjs +27 -3
- package/lib/schema-infer.mjs +16 -2
- package/lib/server/csrf.mjs +14 -2
- package/lib/server/index.mjs +95 -0
- package/lib/service-manager.mjs +39 -14
- package/lib/setup-prompts.mjs +2 -1
- package/lib/setup.mjs +165 -141
- package/lib/storage/backend.mjs +14 -0
- package/lib/storage/unified-storage.mjs +550 -0
- package/lib/template-registry.mjs +73 -0
- package/lib/term-format.mjs +75 -0
- package/lib/uninstall/uninstall.mjs +180 -7
- package/package.json +2 -2
- package/personas/construct.md +7 -8
- package/platforms/claude/settings.template.json +30 -4
- package/platforms/opencode/config.template.json +2 -2
- package/rules/common/neurodivergent-output.md +66 -0
- package/rules/common/tool-invisibility.md +37 -0
- package/scripts/sync-specialists.mjs +381 -95
- package/skills/operating/orchestration-reference.md +2 -16
- package/specialists/policy-inventory.json +14 -0
- package/specialists/prompts/cx-accessibility.md +2 -6
- package/specialists/prompts/cx-ai-engineer.md +0 -4
- package/specialists/prompts/cx-architect.md +3 -5
- package/specialists/prompts/cx-business-strategist.md +0 -5
- package/specialists/prompts/cx-data-analyst.md +0 -4
- package/specialists/prompts/cx-data-engineer.md +0 -4
- package/specialists/prompts/cx-debugger.md +2 -6
- package/specialists/prompts/cx-designer.md +0 -8
- package/specialists/prompts/cx-devil-advocate.md +2 -2
- package/specialists/prompts/cx-docs-keeper.md +0 -13
- package/specialists/prompts/cx-engineer.md +0 -13
- package/specialists/prompts/cx-evaluator.md +2 -2
- package/specialists/prompts/cx-explorer.md +4 -5
- package/specialists/prompts/cx-legal-compliance.md +4 -5
- package/specialists/prompts/cx-operations.md +0 -5
- package/specialists/prompts/cx-orchestrator.md +0 -4
- package/specialists/prompts/cx-platform-engineer.md +0 -8
- package/specialists/prompts/cx-product-manager.md +0 -8
- package/specialists/prompts/cx-qa.md +2 -11
- package/specialists/prompts/cx-rd-lead.md +0 -5
- package/specialists/prompts/cx-release-manager.md +0 -8
- package/specialists/prompts/cx-researcher.md +5 -29
- package/specialists/prompts/cx-reviewer.md +2 -6
- package/specialists/prompts/cx-security.md +2 -11
- package/specialists/prompts/cx-sre.md +0 -15
- package/specialists/prompts/cx-test-automation.md +0 -4
- package/specialists/prompts/cx-trace-reviewer.md +2 -2
- package/specialists/prompts/cx-ux-researcher.md +0 -4
- package/specialists/registry.json +29 -29
- package/templates/distribution/run.mjs +36 -7
- package/templates/docs/accessibility-audit.md +56 -0
- package/templates/docs/architecture-review.md +59 -0
- package/templates/docs/code-review-report.md +46 -0
- package/templates/docs/construct_guide.md +14 -14
- package/templates/docs/debug-investigation.md +53 -0
- package/templates/docs/qa-report.md +48 -0
- package/templates/docs/security-audit-report.md +48 -0
- package/templates/docs/task-packet.md +49 -0
- package/templates/docs/verdict.md +40 -0
- package/lib/server/static/index.html +0 -1
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/agent-instructions/inject.mjs — non-destructive marker-block injection for
|
|
3
|
+
* agent-instruction files the user owns (AGENTS.md, CLAUDE.md) — ADR-0027 §2.
|
|
4
|
+
*
|
|
5
|
+
* Construct's integration guidance lives inside a versioned, hash-stamped block:
|
|
6
|
+
*
|
|
7
|
+
* <!-- BEGIN CONSTRUCT INTEGRATION v:N hash:XXXX -->
|
|
8
|
+
* ...pointers...
|
|
9
|
+
* <!-- END CONSTRUCT INTEGRATION -->
|
|
10
|
+
*
|
|
11
|
+
* Everything outside the markers is preserved byte-for-byte. Injection is
|
|
12
|
+
* idempotent: an existing block with the same version+hash is a no-op; a
|
|
13
|
+
* different hash replaces the block content only; no block appends one. The
|
|
14
|
+
* block dedups against a sibling Beads Integration block — when one is present
|
|
15
|
+
* the tracker line points at it instead of repeating `bd` commands.
|
|
16
|
+
*
|
|
17
|
+
* Mirrors the marker pattern beads-automation uses for its own block, lifted
|
|
18
|
+
* into a reusable primitive so init and sync share one append-or-replace path.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { createHash } from 'node:crypto';
|
|
24
|
+
|
|
25
|
+
export const CONSTRUCT_INTEGRATION_VERSION = 1;
|
|
26
|
+
|
|
27
|
+
const BEGIN_PREFIX = '<!-- BEGIN CONSTRUCT INTEGRATION';
|
|
28
|
+
const END_MARKER = '<!-- END CONSTRUCT INTEGRATION -->';
|
|
29
|
+
const BLOCK_RE = /<!-- BEGIN CONSTRUCT INTEGRATION[^>]*-->[\s\S]*?<!-- END CONSTRUCT INTEGRATION -->/;
|
|
30
|
+
const BEADS_BLOCK_RE = /<!-- BEGIN BEADS INTEGRATION/;
|
|
31
|
+
|
|
32
|
+
function shortHash(body) {
|
|
33
|
+
return createHash('sha256').update(body).digest('hex').slice(0, 12);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// The managed block: concise pointers to the surfaces an agent needs to work
|
|
37
|
+
// with Construct (≤60 lines). When a Beads Integration block is already present
|
|
38
|
+
// the tracker line defers to it rather than duplicating `bd` commands.
|
|
39
|
+
|
|
40
|
+
export function buildConstructIntegrationBody({ hasBeadsBlock = false } = {}) {
|
|
41
|
+
const tracker = hasBeadsBlock
|
|
42
|
+
? '- **Tracker**: see the Beads Integration block below for `bd` commands.'
|
|
43
|
+
: '- **Tracker**: use Beads (`bd`) for all task tracking — run `bd prime` for the workflow. Do not use ad-hoc TODO lists.';
|
|
44
|
+
return [
|
|
45
|
+
'## Construct integration',
|
|
46
|
+
'',
|
|
47
|
+
'This project is managed by Construct. Address `@construct` in your editor and ask',
|
|
48
|
+
'for the outcome, not the specialist — Construct routes to the right specialist chain.',
|
|
49
|
+
'',
|
|
50
|
+
'- **Durable state** lives in `.cx/` (context, knowledge, intake, traces) and Beads.',
|
|
51
|
+
' If services are down, resume from `plan.md`, `.cx/context.md`, the latest',
|
|
52
|
+
' `.cx/handoffs/` file, Beads, and git.',
|
|
53
|
+
'- **Signals**: drop a file into `.cx/inbox/`; `construct intake` classifies and routes it.',
|
|
54
|
+
'- **Specialists** (architect, reviewer, security, …) are dispatched by Construct — you',
|
|
55
|
+
' do not call them directly.',
|
|
56
|
+
tracker,
|
|
57
|
+
'- **Single-writer rule**: one session owns a file\'s edits; others review or wait.',
|
|
58
|
+
'- Run `construct doctor` to check system health.',
|
|
59
|
+
].join('\n');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Inject or update the managed block in `content`. Returns the new content and
|
|
63
|
+
// the action taken (`unchanged` | `updated` | `created`).
|
|
64
|
+
|
|
65
|
+
export function injectConstructBlock(content, body, version = CONSTRUCT_INTEGRATION_VERSION) {
|
|
66
|
+
const hash = shortHash(body);
|
|
67
|
+
const block = `${BEGIN_PREFIX} v:${version} hash:${hash} -->\n${body}\n${END_MARKER}`;
|
|
68
|
+
const existing = content.match(BLOCK_RE);
|
|
69
|
+
if (existing) {
|
|
70
|
+
if (existing[0].includes(`v:${version} hash:${hash}`)) return { content, action: 'unchanged' };
|
|
71
|
+
return { content: content.replace(BLOCK_RE, block), action: 'updated' };
|
|
72
|
+
}
|
|
73
|
+
const sep = content.length === 0 ? '' : content.endsWith('\n') ? '\n' : '\n\n';
|
|
74
|
+
return { content: `${content}${sep}${block}\n`, action: 'created' };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Apply the managed block to a single agent-instruction file. Creates the file
|
|
78
|
+
// (with `header`) when missing; otherwise injects/updates in place, preserving
|
|
79
|
+
// all surrounding content. Writes only when the content changes.
|
|
80
|
+
|
|
81
|
+
export function injectIntoAgentFile(filePath, { version = CONSTRUCT_INTEGRATION_VERSION, header = '' } = {}) {
|
|
82
|
+
const existed = fs.existsSync(filePath);
|
|
83
|
+
const current = existed ? fs.readFileSync(filePath, 'utf8') : '';
|
|
84
|
+
const hasBeadsBlock = BEADS_BLOCK_RE.test(current);
|
|
85
|
+
const body = buildConstructIntegrationBody({ hasBeadsBlock });
|
|
86
|
+
const base = !existed && header ? (header.endsWith('\n') ? header : `${header}\n`) : current;
|
|
87
|
+
const { content, action } = injectConstructBlock(base, body, version);
|
|
88
|
+
const changed = !existed || action !== 'unchanged';
|
|
89
|
+
if (changed) {
|
|
90
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
91
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
92
|
+
}
|
|
93
|
+
return { action: existed ? action : 'created', existed, changed };
|
|
94
|
+
}
|
package/lib/auto-docs.mjs
CHANGED
|
@@ -20,6 +20,14 @@ import { execSync } from 'node:child_process';
|
|
|
20
20
|
|
|
21
21
|
import { CLI_COMMANDS_BY_CATEGORY, CATEGORY_ORDER } from './cli-commands.mjs';
|
|
22
22
|
|
|
23
|
+
// A raw `|` inside a markdown table cell is parsed as a column delimiter, so a
|
|
24
|
+
// description like `--scope=project|user|both` splits the row into phantom
|
|
25
|
+
// columns. Escape pipes (and flatten newlines) for any value emitted into a cell.
|
|
26
|
+
|
|
27
|
+
function tableCell(value) {
|
|
28
|
+
return String(value ?? '').replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
// --- region helpers ---
|
|
24
32
|
|
|
25
33
|
function replaceRegion(content, regionName, newBody) {
|
|
@@ -53,7 +61,7 @@ function buildCommandsTable() {
|
|
|
53
61
|
rows.push('| Command | What it does |');
|
|
54
62
|
rows.push('|---|---|');
|
|
55
63
|
for (const cmd of cmds) {
|
|
56
|
-
rows.push(`| \`construct ${cmd.name}\` | ${cmd.description} |`);
|
|
64
|
+
rows.push(`| \`construct ${cmd.name}\` | ${tableCell(cmd.description)} |`);
|
|
57
65
|
}
|
|
58
66
|
rows.push('');
|
|
59
67
|
}
|
|
@@ -335,7 +343,7 @@ function renderCommandPage(category, commands) {
|
|
|
335
343
|
lines.push('| Command | What it does |');
|
|
336
344
|
lines.push('|---|---|');
|
|
337
345
|
for (const cmd of commands) {
|
|
338
|
-
lines.push(`| \`construct ${cmd.name}\` | ${cmd.description} |`);
|
|
346
|
+
lines.push(`| \`construct ${cmd.name}\` | ${tableCell(cmd.description)} |`);
|
|
339
347
|
}
|
|
340
348
|
lines.push('');
|
|
341
349
|
for (const cmd of commands) {
|
package/lib/cli-commands.mjs
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* - internal: true → hidden from --help and --all; callable but not advertised
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { resolveColors } from './term-format.mjs';
|
|
16
|
+
|
|
15
17
|
export const CLI_COMMANDS = [
|
|
16
18
|
// ── Core (shown by default) ───────────────────────────────────────────
|
|
17
19
|
{
|
|
@@ -26,6 +28,14 @@ export const CLI_COMMANDS = [
|
|
|
26
28
|
{ flag: '--only=<a,b,c>', desc: 'Start only the named services (postgres, dashboard, telemetry, memory, opencode)' },
|
|
27
29
|
],
|
|
28
30
|
},
|
|
31
|
+
{
|
|
32
|
+
name: 'dashboard',
|
|
33
|
+
emoji: '📊',
|
|
34
|
+
category: 'Core',
|
|
35
|
+
core: true,
|
|
36
|
+
description: 'Start the local dashboard/orchestration daemon (or --token to mint a dashboard token)',
|
|
37
|
+
usage: 'construct dashboard [--token]',
|
|
38
|
+
},
|
|
29
39
|
{
|
|
30
40
|
name: 'stop',
|
|
31
41
|
emoji: '⏹',
|
|
@@ -50,12 +60,14 @@ export const CLI_COMMANDS = [
|
|
|
50
60
|
emoji: '🛠️',
|
|
51
61
|
category: 'Core',
|
|
52
62
|
core: true,
|
|
53
|
-
description: 'Machine setup (
|
|
54
|
-
usage: 'construct install [--yes] [--no-docker] [--reconfigure]',
|
|
63
|
+
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]',
|
|
55
65
|
options: [
|
|
56
|
-
{ flag: '--
|
|
57
|
-
{ flag: '--
|
|
58
|
-
{ flag: '--
|
|
66
|
+
{ flag: '--scope=<s>', desc: 'project (default, no-op + guidance) | user (writes ~/.construct/, MCP, ~/.claude/* via consent) | both' },
|
|
67
|
+
{ flag: '--yes', desc: 'Apply defaults without prompts (only meaningful with --scope=user|both)' },
|
|
68
|
+
{ flag: '--no-docker', desc: 'Skip Docker-based service setup (local Postgres)' },
|
|
69
|
+
{ flag: '--no-launch-agent', desc: 'Skip background macOS LaunchAgent registration' },
|
|
70
|
+
{ flag: '--reconfigure', desc: 'Re-prompt for service consent, ignoring cached answers' },
|
|
59
71
|
],
|
|
60
72
|
},
|
|
61
73
|
{
|
|
@@ -68,6 +80,9 @@ export const CLI_COMMANDS = [
|
|
|
68
80
|
options: [
|
|
69
81
|
{ flag: '--yes', desc: 'Accept all defaults (non-interactive)' },
|
|
70
82
|
{ flag: '--no-start', desc: 'Do not start services after init' },
|
|
83
|
+
{ flag: '--commit-bootstrap', desc: 'Keep the beads bootstrap commit (default: leave files uncommitted)' },
|
|
84
|
+
{ flag: '--with-<host>', desc: 'Force an adapter set (claude|codex|opencode|vscode|cursor|copilot); default writes detected hosts only' },
|
|
85
|
+
{ flag: '--all-hosts', desc: 'Write every adapter set regardless of what is installed' },
|
|
71
86
|
{ flag: '--interactive, -i', desc: 'Enable interactive setup with project detection' },
|
|
72
87
|
{ flag: '--quiet, -q', desc: 'Minimal output' },
|
|
73
88
|
{ flag: '--verbose, -v', desc: 'Detailed output' },
|
|
@@ -236,6 +251,8 @@ export const CLI_COMMANDS = [
|
|
|
236
251
|
{ flag: '--fix-legacy-agents', desc: 'Sweep legacy cx-*.md agents at user scope and re-sync' },
|
|
237
252
|
],
|
|
238
253
|
},
|
|
254
|
+
|
|
255
|
+
// ── Work (project workflows) ──────────────────────────────────────────
|
|
239
256
|
{
|
|
240
257
|
name: 'distill',
|
|
241
258
|
emoji: '🔬',
|
|
@@ -252,6 +269,14 @@ export const CLI_COMMANDS = [
|
|
|
252
269
|
description: 'Convert documents to indexed markdown',
|
|
253
270
|
usage: 'construct ingest <file> [--strategy=adapter|provider] [--orchestration=prompt-only|orchestrated] [--strict] [--legacy-extractor]',
|
|
254
271
|
},
|
|
272
|
+
{
|
|
273
|
+
name: 'export',
|
|
274
|
+
emoji: '📤',
|
|
275
|
+
category: 'Work',
|
|
276
|
+
core: false,
|
|
277
|
+
description: 'Export markdown to PDF/DOCX/HTML via Pandoc + Typst (optional system binaries; ADR-0024)',
|
|
278
|
+
usage: 'construct export <markdown-file> --to=<pdf|docx|html> [--output=<path>]',
|
|
279
|
+
},
|
|
255
280
|
{
|
|
256
281
|
name: 'infer',
|
|
257
282
|
emoji: '🧠',
|
|
@@ -339,13 +364,21 @@ export const CLI_COMMANDS = [
|
|
|
339
364
|
emoji: '🎼',
|
|
340
365
|
category: 'Models & Integrations',
|
|
341
366
|
core: false,
|
|
342
|
-
description: 'Construct-owned local orchestration runtime
|
|
343
|
-
usage: 'construct orchestrate <run|status> [options]',
|
|
367
|
+
description: 'Construct-owned local orchestration runtime, in-process or against the local daemon (--remote)',
|
|
368
|
+
usage: 'construct orchestrate <run|status> [options] [--remote]',
|
|
344
369
|
subcommands: [
|
|
345
|
-
{ name: 'run "<request>" [--strategy S] [--host H] [--no-construct] [--no-execute] [--json]', desc: 'Plan and run a request through a Construct-owned specialist chain;
|
|
346
|
-
{ name: 'status [run-id] [--json]', desc: 'Inspect a run, or list recent runs' },
|
|
370
|
+
{ name: 'run "<request>" [--strategy S] [--host H] [--worker-backend provider] [--no-construct] [--no-execute] [--json] [--remote]', desc: 'Plan and run a request through a Construct-owned specialist chain; --remote drives the local daemon over HTTP' },
|
|
371
|
+
{ name: 'status [run-id] [--json] [--remote]', desc: 'Inspect a run, or list recent runs (locally or from the daemon)' },
|
|
347
372
|
],
|
|
348
373
|
},
|
|
374
|
+
{
|
|
375
|
+
name: 'acp',
|
|
376
|
+
emoji: '🔗',
|
|
377
|
+
category: 'Models & Integrations',
|
|
378
|
+
core: false,
|
|
379
|
+
description: 'Run Construct as an Agent Client Protocol (ACP) server over stdio for Zed/JetBrains/VS Code ACP clients',
|
|
380
|
+
usage: 'construct acp',
|
|
381
|
+
},
|
|
349
382
|
{
|
|
350
383
|
name: 'mcp',
|
|
351
384
|
emoji: '🔌',
|
|
@@ -965,17 +998,12 @@ export function getCommandSpec(name) {
|
|
|
965
998
|
return CLI_COMMANDS.find((c) => c.name === name) || null;
|
|
966
999
|
}
|
|
967
1000
|
|
|
968
|
-
const ESC = String.fromCharCode(27);
|
|
969
|
-
|
|
970
1001
|
// Render a per-command help block. Pulls usage, description, subcommands,
|
|
971
1002
|
// and options from the CLI_COMMANDS entry so help stays consistent across
|
|
972
1003
|
// every command. Internal commands fall back to a minimal block.
|
|
973
1004
|
|
|
974
1005
|
export function formatCommandHelp(name, { colors = false } = {}) {
|
|
975
|
-
const
|
|
976
|
-
const c = useColor
|
|
977
|
-
? { bold: `${ESC}[1m`, dim: `${ESC}[2m`, reset: `${ESC}[0m` }
|
|
978
|
-
: { bold: '', dim: '', reset: '' };
|
|
1006
|
+
const c = resolveColors({ enabled: colors });
|
|
979
1007
|
const spec = getCommandSpec(name);
|
|
980
1008
|
if (!spec) {
|
|
981
1009
|
return `Unknown command: ${name}\n\nRun 'construct --help' for available commands.\n`;
|
package/lib/comment-lint.mjs
CHANGED
|
@@ -52,6 +52,38 @@ function isArtifactPath(rel) {
|
|
|
52
52
|
return ARTIFACT_PATH_GLOBS.some((r) => r.test(rel));
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// Deliverable surfaces where a tool-identity leak (rules/common/tool-invisibility.md)
|
|
56
|
+
// matters: project docs and the durable .cx knowledge / research / handoff / strategy
|
|
57
|
+
// stores. Broader than the fabrication-scoped ARTIFACT_PATH_GLOBS because a strategy
|
|
58
|
+
// lands in docs/ root, not only docs/prd.
|
|
59
|
+
|
|
60
|
+
const DELIVERABLE_LEAK_GLOBS = [
|
|
61
|
+
/^docs\/.*\.md$/,
|
|
62
|
+
/^\.cx\/(?:research|knowledge|handoffs)\//,
|
|
63
|
+
/^\.cx\/strategy\.md$/,
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
function isDeliverablePath(rel) {
|
|
67
|
+
return DELIVERABLE_LEAK_GLOBS.some((r) => r.test(rel));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// The Construct repo legitimately names Construct and its cx-* roles in its own docs —
|
|
71
|
+
// the tool-identity check is for consuming projects only. Identify self by package name,
|
|
72
|
+
// memoized per rootDir so a repo-wide lint reads package.json once, not once per file.
|
|
73
|
+
|
|
74
|
+
const selfRepoCache = new Map();
|
|
75
|
+
|
|
76
|
+
function isConstructSelfRepo(rootDir) {
|
|
77
|
+
if (selfRepoCache.has(rootDir)) return selfRepoCache.get(rootDir);
|
|
78
|
+
let isSelf = false;
|
|
79
|
+
try {
|
|
80
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8'));
|
|
81
|
+
isSelf = pkg.name === '@geraldmaron/construct';
|
|
82
|
+
} catch { isSelf = false; }
|
|
83
|
+
selfRepoCache.set(rootDir, isSelf);
|
|
84
|
+
return isSelf;
|
|
85
|
+
}
|
|
86
|
+
|
|
55
87
|
function requiresHeader(rel) {
|
|
56
88
|
const ext = path.extname(rel);
|
|
57
89
|
if (['.yaml', '.yml', '.json', '.jsonl', '.toml'].includes(ext)) return { required: false, type: null };
|
|
@@ -284,6 +316,77 @@ function checkArtifactBanned(content, rel) {
|
|
|
284
316
|
return violations;
|
|
285
317
|
}
|
|
286
318
|
|
|
319
|
+
// Internal specialist role ids (cx-business-strategist, cx-product-manager, …) are
|
|
320
|
+
// implementation detail; in a consuming project's deliverable they read as a tool-identity
|
|
321
|
+
// leak. The set is anchored to the real role ids from specialists/registry.json — an
|
|
322
|
+
// open-ended /cx-[a-z-]+/ also matches unrelated npm packages (cx-oracle, cx-ray, cx-pro)
|
|
323
|
+
// and would fail the release gate on legitimate user content. Drift from the registry is
|
|
324
|
+
// caught by tests/tool-invisibility.test.mjs, not at runtime: this module sits on the hook
|
|
325
|
+
// path and must never throw on a missing or malformed registry file.
|
|
326
|
+
|
|
327
|
+
export const KNOWN_CX_ROLE_IDS = [
|
|
328
|
+
'accessibility', 'ai-engineer', 'architect', 'business-strategist', 'data-analyst',
|
|
329
|
+
'data-engineer', 'debugger', 'designer', 'devil-advocate', 'docs-keeper', 'engineer',
|
|
330
|
+
'evaluator', 'explorer', 'legal-compliance', 'operations', 'orchestrator',
|
|
331
|
+
'platform-engineer', 'product-manager', 'qa', 'rd-lead', 'release-manager', 'researcher',
|
|
332
|
+
'reviewer', 'security', 'sre', 'test-automation', 'trace-reviewer', 'ux-researcher',
|
|
333
|
+
];
|
|
334
|
+
|
|
335
|
+
const CX_ROLE_LEAK = new RegExp(
|
|
336
|
+
`\\bcx-(?:${[...KNOWN_CX_ROLE_IDS].sort((a, b) => b.length - a.length).join('|')})\\b`,
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
// Code fences (``` or ~~~) wrap non-prose content, so a role id inside one is not a leak.
|
|
340
|
+
// Only closed fences are skipped: an unclosed fence must not suppress scanning of the rest
|
|
341
|
+
// of the document, or the backstop would fail open on a leak. Returns the line indices that
|
|
342
|
+
// fall inside a properly terminated fence.
|
|
343
|
+
|
|
344
|
+
function closedFenceLines(lines) {
|
|
345
|
+
const inside = new Set();
|
|
346
|
+
let openChar = null;
|
|
347
|
+
let pending = [];
|
|
348
|
+
for (let i = 0; i < lines.length; i++) {
|
|
349
|
+
const marker = lines[i].match(/^(```+|~~~+)/);
|
|
350
|
+
if (openChar === null) {
|
|
351
|
+
if (marker) { openChar = marker[1][0]; pending = [i]; }
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
pending.push(i);
|
|
355
|
+
if (marker && marker[1][0] === openChar) {
|
|
356
|
+
for (const idx of pending) inside.add(idx);
|
|
357
|
+
openChar = null;
|
|
358
|
+
pending = [];
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return inside;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function findToolIdentityLeaks(content) {
|
|
365
|
+
const lines = content.split('\n');
|
|
366
|
+
const fenced = closedFenceLines(lines);
|
|
367
|
+
const violations = [];
|
|
368
|
+
let inFrontmatter = false;
|
|
369
|
+
let inComment = false;
|
|
370
|
+
for (let i = 0; i < lines.length; i++) {
|
|
371
|
+
const line = lines[i];
|
|
372
|
+
if (i === 0 && line.trim() === '---') { inFrontmatter = true; continue; }
|
|
373
|
+
if (inFrontmatter) { if (line.trim() === '---') inFrontmatter = false; continue; }
|
|
374
|
+
if (fenced.has(i)) continue;
|
|
375
|
+
if (inComment) { if (line.includes('-->')) inComment = false; continue; }
|
|
376
|
+
if (line.includes('construct-lint-ignore')) continue;
|
|
377
|
+
const scan = line.replace(/<!--.*?-->/g, '');
|
|
378
|
+
if (line.includes('<!--') && !line.includes('-->')) inComment = true;
|
|
379
|
+
if (CX_ROLE_LEAK.test(scan)) {
|
|
380
|
+
violations.push({
|
|
381
|
+
line: i + 1,
|
|
382
|
+
label: 'tool-identity leak: internal cx-* specialist role id in a deliverable — the artifact is about the user\'s project, not Construct\'s roles (tool-invisibility §2)',
|
|
383
|
+
kind: 'artifact',
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return violations;
|
|
388
|
+
}
|
|
389
|
+
|
|
287
390
|
function checkBanned(content, filePath) {
|
|
288
391
|
if (isBannedCheckSkipped(filePath)) return [];
|
|
289
392
|
|
|
@@ -366,6 +469,18 @@ export function lintFile(filePath, { rootDir = process.cwd(), fix = false } = {}
|
|
|
366
469
|
for (const v of artifactHits) bucket.push(v);
|
|
367
470
|
}
|
|
368
471
|
|
|
472
|
+
// Tool-identity leak (rules/common/tool-invisibility.md): consuming-project deliverables
|
|
473
|
+
// only — skipped on the Construct repo, where naming Construct and cx-* roles is correct.
|
|
474
|
+
// Same mode routing as the artifact lint (warn by default, block in the release gate).
|
|
475
|
+
if (!isConstructSelfRepo(rootDir) && isDeliverablePath(rel)) {
|
|
476
|
+
const leaks = findToolIdentityLeaks(content);
|
|
477
|
+
if (leaks.length > 0) {
|
|
478
|
+
const mode = process.env.CONSTRUCT_ARTIFACT_LINT_MODE || 'warn';
|
|
479
|
+
const bucket = mode === 'block' ? errors : warnings;
|
|
480
|
+
for (const v of leaks) bucket.push(v);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
369
484
|
return { path: rel, errors, warnings };
|
|
370
485
|
}
|
|
371
486
|
|
package/lib/completions.mjs
CHANGED
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
* Outputs to a platform-appropriate per-user completions directory.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { CLI_COMMANDS } from './cli-commands.mjs';
|
|
8
|
+
import { CLI_COMMANDS as ALL_CLI_COMMANDS } from './cli-commands.mjs';
|
|
9
|
+
|
|
10
|
+
// Completions advertise only public commands. internal: true commands stay
|
|
11
|
+
// callable but never appear as tab candidates (the cli-commands.mjs visibility
|
|
12
|
+
// contract). Shadowing the imported list keeps every generator below public-only.
|
|
13
|
+
|
|
14
|
+
const CLI_COMMANDS = ALL_CLI_COMMANDS.filter((c) => !c.internal);
|
|
9
15
|
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
10
16
|
import { join } from 'path';
|
|
11
17
|
import { homedir } from 'os';
|
package/lib/config/schema.mjs
CHANGED
|
@@ -30,6 +30,7 @@ export const INGEST_ORCHESTRATIONS = ['prompt-only', 'orchestrated'];
|
|
|
30
30
|
|
|
31
31
|
export const ORCHESTRATION_WORKER_BACKENDS = ['inline', 'provider'];
|
|
32
32
|
export const ORCHESTRATION_STORES = ['filesystem', 'sqlite', 'postgres'];
|
|
33
|
+
export const CHAIN_OF_THOUGHT_MODES = ['hidden', 'surface', 'telemetry_only'];
|
|
33
34
|
|
|
34
35
|
// SessionStart context routing. `auto` keeps the rich payload on stdout for
|
|
35
36
|
// interactive sessions and suppresses it (to a debug log) for non-interactive /
|
|
@@ -56,6 +57,7 @@ export const DEFAULT_PROJECT_CONFIG = Object.freeze({
|
|
|
56
57
|
orchestration: Object.freeze({
|
|
57
58
|
workerBackend: 'inline',
|
|
58
59
|
store: 'filesystem',
|
|
60
|
+
chainOfThought: 'hidden',
|
|
59
61
|
}),
|
|
60
62
|
telemetry: Object.freeze({
|
|
61
63
|
enabled: true,
|
|
@@ -93,6 +95,7 @@ export const DEFAULT_PROJECT_CONFIG = Object.freeze({
|
|
|
93
95
|
});
|
|
94
96
|
|
|
95
97
|
export const FIELD_RULES = {
|
|
98
|
+
$schema: { type: 'string', required: false },
|
|
96
99
|
version: { type: 'number', required: true },
|
|
97
100
|
alias: { type: 'string', required: false, maxLength: 120 },
|
|
98
101
|
deployment: {
|
|
@@ -123,6 +126,7 @@ export const FIELD_RULES = {
|
|
|
123
126
|
fields: {
|
|
124
127
|
workerBackend: { type: 'string', enum: ORCHESTRATION_WORKER_BACKENDS },
|
|
125
128
|
store: { type: 'string', enum: ORCHESTRATION_STORES },
|
|
129
|
+
chainOfThought: { type: 'string', enum: CHAIN_OF_THOUGHT_MODES },
|
|
126
130
|
},
|
|
127
131
|
},
|
|
128
132
|
telemetry: {
|
package/lib/docs-verify.mjs
CHANGED
|
@@ -122,26 +122,23 @@ function checkReadmeBasicSections(readmePath) {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
// AGENTS.md is user-owned (ADR-0027 §2): a host project carries bd's skeleton
|
|
126
|
+
// plus Construct's fenced integration block, while the meta-repo carries its own
|
|
127
|
+
// hand-written contract. Neither has a fixed section list, so verify substance —
|
|
128
|
+
// a heading and a non-trivial body past any frontmatter — not a prescribed shape.
|
|
129
|
+
|
|
130
|
+
function checkAgentsPresent(agentsPath) {
|
|
126
131
|
if (!fs.existsSync(agentsPath)) return false;
|
|
127
|
-
|
|
132
|
+
|
|
128
133
|
try {
|
|
129
|
-
const content = readFileSync(agentsPath, "utf8")
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
"end-of-session rules",
|
|
135
|
-
"verification rules"
|
|
136
|
-
];
|
|
137
|
-
|
|
138
|
-
const missingSections = requiredSections.filter(section => !content.includes(section));
|
|
139
|
-
|
|
140
|
-
if (missingSections.length > 0) {
|
|
141
|
-
warnings.push(`AGENTS.md missing sections: ${missingSections.join(", ")} in ${path.relative(cwd, agentsPath)}`);
|
|
134
|
+
const content = readFileSync(agentsPath, "utf8");
|
|
135
|
+
const body = content.replace(/^---[\s\S]*?\n---\s*/, "").trim();
|
|
136
|
+
|
|
137
|
+
if (!/^#\s/m.test(body) || body.length < 40) {
|
|
138
|
+
warnings.push(`AGENTS.md is empty or lacks a usable agent guide: ${path.relative(cwd, agentsPath)}`);
|
|
142
139
|
return false;
|
|
143
140
|
}
|
|
144
|
-
|
|
141
|
+
|
|
145
142
|
return true;
|
|
146
143
|
} catch (err) {
|
|
147
144
|
warnings.push(`Cannot read AGENTS.md: ${path.relative(cwd, agentsPath)} (${err.message})`);
|
|
@@ -389,7 +386,7 @@ function main() {
|
|
|
389
386
|
if (!quickMode) {
|
|
390
387
|
// Detailed checks (warnings only)
|
|
391
388
|
checkReadmeBasicSections(path.join(cwd, "README.md"));
|
|
392
|
-
|
|
389
|
+
checkAgentsPresent(path.join(cwd, "AGENTS.md"));
|
|
393
390
|
if (planExists) checkPlanLinkedToIssues(path.join(cwd, "plan.md"));
|
|
394
391
|
if (cxExists && fs.existsSync(contextMdPath)) checkContextHasProgress(contextMdPath);
|
|
395
392
|
checkDocsSystem(path.join(cwd, "docs"));
|
|
@@ -454,7 +451,7 @@ export {
|
|
|
454
451
|
checkExists,
|
|
455
452
|
checkFileAge,
|
|
456
453
|
checkReadmeBasicSections,
|
|
457
|
-
|
|
454
|
+
checkAgentsPresent,
|
|
458
455
|
checkPlanLinkedToIssues,
|
|
459
456
|
checkContextHasProgress,
|
|
460
457
|
checkDocsSystem,
|
package/lib/doctor/cli.mjs
CHANGED
|
@@ -94,6 +94,13 @@ export async function runCli(args) {
|
|
|
94
94
|
return 0;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
if (sub === 'bench:hooks') {
|
|
98
|
+
const { main } = await import('../../scripts/bench-hooks.mjs');
|
|
99
|
+
const report = await main();
|
|
100
|
+
const failed = report.results.filter((r) => r.status === 'fail');
|
|
101
|
+
return failed.length > 0 ? 1 : 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
97
104
|
if (sub === 'consistency') {
|
|
98
105
|
const { runAllChecks } = await import('./watchers/consistency.mjs');
|
|
99
106
|
const result = await runAllChecks();
|
|
@@ -109,6 +116,6 @@ export async function runCli(args) {
|
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
console.error(`Unknown doctor subcommand: ${sub}`);
|
|
112
|
-
console.error('Usage: construct doctor [status|watch|stop|logs|tick|consistency]');
|
|
119
|
+
console.error('Usage: construct doctor [status|watch|stop|logs|tick|consistency|bench:hooks]');
|
|
113
120
|
return 2;
|
|
114
121
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/document-export.mjs — markdown → PDF / DOCX / HTML export via external
|
|
3
|
+
* binaries (Pandoc + Typst), per ADR-0024.
|
|
4
|
+
*
|
|
5
|
+
* Bound to system-binary tooling discovered at runtime, never bundled in core
|
|
6
|
+
* (ADR-0001 zero-npm-core; ADR-0014 optional-capability pattern). Both engines
|
|
7
|
+
* are spawned as separate processes — Pandoc isolates its GPLv2+ licence at
|
|
8
|
+
* the process boundary; Typst is the Apache-2.0 PDF engine driven through
|
|
9
|
+
* Pandoc's `--pdf-engine=typst` flag, so a single Pandoc invocation produces
|
|
10
|
+
* every target format the contract supports.
|
|
11
|
+
*
|
|
12
|
+
* detect(format) reports availability without spawning the engine for real.
|
|
13
|
+
* exportMarkdown({ inputPath, outputPath, format, env }) spawns the engine and
|
|
14
|
+
* returns a structured result. Both surface actionable "install X" guidance
|
|
15
|
+
* when a required binary is absent; neither throws on missing tooling.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { spawnSync } from 'node:child_process';
|
|
21
|
+
|
|
22
|
+
export const EXPORT_FORMATS = ['pdf', 'docx', 'html'];
|
|
23
|
+
|
|
24
|
+
const FORMAT_ENGINES = {
|
|
25
|
+
pdf: { engine: 'pandoc', pdfEngine: 'typst', extraBinaries: ['typst'] },
|
|
26
|
+
docx: { engine: 'pandoc', pdfEngine: null, extraBinaries: [] },
|
|
27
|
+
html: { engine: 'pandoc', pdfEngine: null, extraBinaries: [] },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function whichBin(name, env = process.env) {
|
|
31
|
+
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
32
|
+
const result = spawnSync(cmd, [name], { encoding: 'utf8', env });
|
|
33
|
+
if (result.status !== 0) return null;
|
|
34
|
+
const first = (result.stdout || '').trim().split('\n')[0];
|
|
35
|
+
return first || null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function binVersion(name, args = ['--version'], env = process.env) {
|
|
39
|
+
const r = spawnSync(name, args, { encoding: 'utf8', env });
|
|
40
|
+
if (r.status !== 0) return null;
|
|
41
|
+
const line = (r.stdout || '').trim().split('\n')[0];
|
|
42
|
+
return line || null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function installHint(name) {
|
|
46
|
+
if (name === 'pandoc') return 'Install pandoc to enable document export (e.g. `brew install pandoc` on macOS, `apt install pandoc` on Debian/Ubuntu, or https://pandoc.org/installing.html).';
|
|
47
|
+
if (name === 'typst') return 'Install typst to enable PDF export via Pandoc (`brew install typst` on macOS, https://github.com/typst/typst/releases for binaries).';
|
|
48
|
+
return `Install ${name} to enable this export format.`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// detect returns the same structure regardless of format so callers can render
|
|
52
|
+
// availability uniformly. `present` is the single boolean a caller should gate
|
|
53
|
+
// on; `missing` enumerates the binaries to install when present is false.
|
|
54
|
+
|
|
55
|
+
export function detect(format, env = process.env) {
|
|
56
|
+
const config = FORMAT_ENGINES[format];
|
|
57
|
+
if (!config) return { ok: false, format, present: false, missing: [], message: `Unsupported format: ${format}. Supported: ${EXPORT_FORMATS.join(', ')}.` };
|
|
58
|
+
const required = [config.engine, ...(config.extraBinaries || [])];
|
|
59
|
+
const status = required.map((name) => ({ name, path: whichBin(name, env), version: null }));
|
|
60
|
+
for (const s of status) if (s.path) s.version = binVersion(s.name, ['--version'], env);
|
|
61
|
+
const missing = status.filter((s) => !s.path).map((s) => s.name);
|
|
62
|
+
return {
|
|
63
|
+
ok: true,
|
|
64
|
+
format,
|
|
65
|
+
present: missing.length === 0,
|
|
66
|
+
binaries: status,
|
|
67
|
+
missing,
|
|
68
|
+
message: missing.length === 0
|
|
69
|
+
? `Ready: ${status.map((s) => `${s.name} (${s.version?.split(' ').slice(0, 2).join(' ') ?? 'unknown'})`).join(', ')}`
|
|
70
|
+
: missing.map(installHint).join(' '),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function defaultOutputPath(inputPath, format) {
|
|
75
|
+
const parsed = path.parse(inputPath);
|
|
76
|
+
return path.join(parsed.dir, `${parsed.name}.${format}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// exportMarkdown is the single contract callers depend on (CLI / MCP / SDK).
|
|
80
|
+
// Returns { ok, format, inputPath, outputPath, engine, missing?, message }.
|
|
81
|
+
// ok=false with a populated `missing` means a required binary is absent — the
|
|
82
|
+
// caller surfaces `message` (which carries the install hint) instead of
|
|
83
|
+
// retrying or crashing.
|
|
84
|
+
|
|
85
|
+
export function exportMarkdown({ inputPath, outputPath, format, env = process.env, spawnFn = spawnSync } = {}) {
|
|
86
|
+
if (!inputPath) return { ok: false, format, message: 'exportMarkdown: inputPath is required.' };
|
|
87
|
+
if (!EXPORT_FORMATS.includes(format)) return { ok: false, format, inputPath, message: `Unsupported format: ${format}. Supported: ${EXPORT_FORMATS.join(', ')}.` };
|
|
88
|
+
if (!fs.existsSync(inputPath)) return { ok: false, format, inputPath, message: `exportMarkdown: input does not exist: ${inputPath}` };
|
|
89
|
+
|
|
90
|
+
const detection = detect(format, env);
|
|
91
|
+
if (!detection.present) {
|
|
92
|
+
return { ok: false, format, inputPath, missing: detection.missing, message: detection.message };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const config = FORMAT_ENGINES[format];
|
|
96
|
+
const target = outputPath || defaultOutputPath(inputPath, format);
|
|
97
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
98
|
+
|
|
99
|
+
const args = ['-f', 'markdown', '-o', target, '--standalone'];
|
|
100
|
+
if (config.pdfEngine) args.push(`--pdf-engine=${config.pdfEngine}`);
|
|
101
|
+
args.push(inputPath);
|
|
102
|
+
|
|
103
|
+
const result = spawnFn(config.engine, args, { encoding: 'utf8', env });
|
|
104
|
+
if (result.status !== 0) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
format,
|
|
108
|
+
inputPath,
|
|
109
|
+
outputPath: target,
|
|
110
|
+
engine: config.engine,
|
|
111
|
+
message: `Export failed (${config.engine} exit ${result.status}): ${(result.stderr || result.stdout || '').trim().slice(0, 400)}`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
ok: true,
|
|
117
|
+
format,
|
|
118
|
+
inputPath,
|
|
119
|
+
outputPath: target,
|
|
120
|
+
engine: config.engine,
|
|
121
|
+
pdfEngine: config.pdfEngine,
|
|
122
|
+
message: `Wrote ${path.relative(process.cwd(), target)}`,
|
|
123
|
+
};
|
|
124
|
+
}
|
package/lib/embed/daemon.mjs
CHANGED
|
@@ -1077,7 +1077,7 @@ this.#scheduler.register(
|
|
|
1077
1077
|
method: 'POST',
|
|
1078
1078
|
headers: {
|
|
1079
1079
|
Authorization: `Bearer ${orKey}`, 'content-type': 'application/json',
|
|
1080
|
-
'HTTP-Referer': 'https://github.com/construct',
|
|
1080
|
+
'HTTP-Referer': 'https://github.com/geraldmaron/construct',
|
|
1081
1081
|
},
|
|
1082
1082
|
body: JSON.stringify({
|
|
1083
1083
|
model: modelId.replace(/^openrouter\//, ''),
|