@geraldmaron/construct 1.2.1 → 1.2.3
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 -6
- package/apps/chat/engine/ai-sdk-agent.mjs +183 -0
- package/apps/chat/engine/loop-driver.mjs +211 -0
- package/apps/chat/engine/models.mjs +122 -0
- package/apps/chat/engine/provider-adapters.mjs +171 -0
- package/apps/chat/engine/tools/permission.mjs +54 -0
- package/apps/chat/engine/tools/primitives.mjs +180 -0
- package/apps/chat/engine/tools/registry.mjs +122 -0
- package/apps/chat/engine/turn-controls.mjs +70 -0
- package/bin/construct +93 -79
- package/commands/plan/decide.md +1 -1
- package/commands/remember/runbook.md +1 -1
- package/examples/README.md +1 -1
- package/examples/distribution/README.md +1 -1
- package/examples/distribution/sources/adr.md +2 -2
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/examples/distribution/sources/prd-platform.md +1 -1
- package/examples/distribution/sources/research-brief.md +2 -2
- package/examples/distribution/sources/rfc-platform.md +1 -1
- package/examples/distribution/sources/runbook.md +2 -2
- package/examples/distribution/sources/strategy.md +1 -1
- package/lib/agent-instructions/inject.mjs +1 -1
- package/lib/artifact-type-from-path.mjs +9 -5
- package/lib/audit-specialists.mjs +1 -1
- package/lib/audit-trail.mjs +3 -3
- package/lib/auto-docs.mjs +10 -10
- package/lib/bootstrap/built-ins.mjs +3 -3
- package/lib/boundary.mjs +3 -2
- package/lib/brand-prose.mjs +1 -1
- package/lib/cache-governor.js +4 -4
- package/lib/certification/document-io-fixtures.mjs +1 -1
- package/lib/chat/config.mjs +2 -2
- package/lib/chat/desktop-binary.mjs +5 -3
- package/lib/cli-commands.mjs +4 -6
- package/lib/cli-service-inventory.mjs +1 -1
- package/lib/comment-lint.mjs +9 -5
- package/lib/config/alias.mjs +3 -2
- package/lib/config/intake-policy.mjs +8 -78
- package/lib/config/legacy-config-migration.mjs +59 -0
- package/lib/config/project-config.mjs +95 -5
- package/lib/config/schema.mjs +9 -19
- package/lib/config/xdg.mjs +63 -0
- package/lib/contracts/violation-log.mjs +2 -2
- package/lib/cost-ledger.mjs +4 -3
- package/lib/cost.mjs +3 -2
- package/lib/decisions/registry.mjs +3 -3
- package/lib/distill.mjs +2 -2
- package/lib/docs-verify.mjs +2 -0
- package/lib/doctor/audit.mjs +4 -3
- package/lib/doctor/cli.mjs +0 -1
- package/lib/doctor/index.mjs +4 -4
- package/lib/doctor/report.mjs +3 -3
- package/lib/doctor/watchers/bd-watch.mjs +5 -5
- package/lib/doctor/watchers/cost.mjs +4 -4
- package/lib/doctor/watchers/disk.mjs +4 -3
- package/lib/doctor/watchers/service-health.mjs +5 -5
- package/lib/document-export.mjs +1 -1
- package/lib/document-extract.mjs +3 -3
- package/lib/document-ingest.mjs +13 -3
- package/lib/efficiency.mjs +2 -1
- package/lib/embed/approval-queue.mjs +3 -2
- package/lib/embed/cli.mjs +7 -5
- package/lib/embed/config.mjs +6 -5
- package/lib/embed/daemon.mjs +6 -5
- package/lib/embed/inbox-live-watcher.mjs +1 -1
- package/lib/embed/inbox.mjs +52 -37
- package/lib/embed/intake-metrics.mjs +3 -2
- package/lib/embed/reconcile.mjs +1 -1
- package/lib/embed/semantic.mjs +3 -2
- package/lib/embed/supervision.mjs +4 -2
- package/lib/engine/registry.mjs +3 -3
- package/lib/env-config.mjs +34 -12
- package/lib/features.mjs +4 -3
- package/lib/health-check.mjs +7 -6
- package/lib/hook-health.mjs +5 -4
- package/lib/hooks/_lib/log.mjs +4 -3
- package/lib/hooks/_lib/output-mode.mjs +2 -1
- package/lib/hooks/agent-tracker.mjs +3 -4
- package/lib/hooks/audit-reads.mjs +2 -2
- package/lib/hooks/audit-trail.mjs +2 -2
- package/lib/hooks/bash-output-logger.mjs +3 -3
- package/lib/hooks/brand-prose-lint.mjs +1 -1
- package/lib/hooks/ci-status-check.mjs +3 -2
- package/lib/hooks/context-watch.mjs +2 -2
- package/lib/hooks/context-window-recovery.mjs +5 -5
- package/lib/hooks/dep-audit.mjs +2 -2
- package/lib/hooks/doc-coupling-check.mjs +3 -2
- package/lib/hooks/edit-accumulator.mjs +3 -2
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/guard-bash.mjs +2 -2
- package/lib/hooks/model-fallback.mjs +2 -1
- package/lib/hooks/policy-engine.mjs +4 -3
- package/lib/hooks/pre-compact.mjs +7 -7
- package/lib/hooks/readme-age-check.mjs +2 -2
- package/lib/hooks/session-optimize.mjs +4 -3
- package/lib/hooks/session-start.mjs +6 -5
- package/lib/hooks/stop-notify.mjs +9 -8
- package/lib/hooks/stop-typecheck.mjs +4 -3
- package/lib/hooks/test-watch.mjs +2 -2
- package/lib/host-disposition.mjs +1 -1
- package/lib/ingest/provider-extract.mjs +2 -1
- package/lib/init/detect-existing-structure.mjs +2 -2
- package/lib/init/doc-lanes.mjs +3 -10
- package/lib/init-docs.mjs +12 -54
- package/lib/init-unified.mjs +35 -81
- package/lib/init.mjs +7 -5
- package/lib/install/desktop-binary-download.mjs +5 -2
- package/lib/intake/daemon.mjs +31 -8
- package/lib/intake/intake-config.mjs +5 -32
- package/lib/integrations/intake-integrations.mjs +8 -7
- package/lib/knowledge/rag.mjs +2 -2
- package/lib/knowledge/search.mjs +15 -15
- package/lib/maintenance/cleanup.mjs +14 -10
- package/lib/mcp/server.mjs +1 -1
- package/lib/mcp/tools/project.mjs +2 -1
- package/lib/model-cheapest-provider.mjs +4 -3
- package/lib/model-pricing.mjs +3 -2
- package/lib/model-router.mjs +4 -3
- package/lib/models/catalog.mjs +2 -1
- package/lib/models/execution-capability-profile.mjs +2 -1
- package/lib/models/provider-poll.mjs +2 -1
- package/lib/ollama-manager.mjs +0 -1
- package/lib/op-log.mjs +2 -1
- package/lib/opencode-runtime-plugin.mjs +3 -2
- package/lib/oracle/artifact-gate.mjs +3 -0
- package/lib/oracle/cli.mjs +2 -1
- package/lib/oracle/execute.mjs +3 -2
- package/lib/oracle/index.mjs +2 -1
- package/lib/oracle/read-model.mjs +3 -2
- package/lib/performance/generate.mjs +4 -3
- package/lib/platforms/capabilities.mjs +1 -1
- package/lib/plugin-registry.mjs +2 -1
- package/lib/profiles/lifecycle.mjs +3 -3
- package/lib/project-profile.mjs +2 -1
- package/lib/project-root.mjs +9 -7
- package/lib/provider-capabilities.js +4 -3
- package/lib/providers/auth-manager.mjs +2 -1
- package/lib/providers/copilot-auth.mjs +3 -2
- package/lib/providers/creds.mjs +3 -2
- package/lib/providers/registry.mjs +3 -3
- package/lib/providers/secret-resolver.mjs +3 -2
- package/lib/read-tracker-store.mjs +2 -1
- package/lib/reconcile/mcp-entry-reconcile.mjs +2 -2
- package/lib/reflect.mjs +2 -1
- package/lib/registry/generate-docs.mjs +2 -2
- package/lib/roles/approval-surface.mjs +5 -4
- package/lib/roles/event-bus.mjs +0 -1
- package/lib/roles/gateway.mjs +6 -2
- package/lib/rules-delivery.mjs +1 -1
- package/lib/runtime-env.mjs +1 -1
- package/lib/runtime-pressure.mjs +5 -3
- package/lib/sandbox.mjs +3 -2
- package/lib/scheduler/solo.mjs +6 -4
- package/lib/server/auth.mjs +4 -3
- package/lib/server/index.mjs +24 -26
- package/lib/server/insights.mjs +5 -4
- package/lib/server/webhook.mjs +2 -1
- package/lib/service-manager.mjs +5 -3
- package/lib/setup.mjs +21 -11
- package/lib/status.mjs +6 -5
- package/lib/storage/embeddings-local.mjs +3 -2
- package/lib/storage/sync.mjs +2 -2
- package/lib/telemetry/client.mjs +0 -1
- package/lib/telemetry/intent-verifications.mjs +6 -6
- package/lib/telemetry/model-pricing-catalog.mjs +4 -3
- package/lib/telemetry/rule-calls.mjs +3 -3
- package/lib/telemetry/skill-calls.mjs +3 -3
- package/lib/template-registry.mjs +1 -0
- package/lib/templates/visual-requirements.mjs +1 -1
- package/lib/test-corpus-inventory.mjs +1 -1
- package/lib/uninstall/uninstall.mjs +12 -10
- package/package.json +4 -3
- package/platforms/claude/settings.template.json +43 -43
- package/rules/common/no-fabrication.md +1 -1
- package/rules/common/release-gates.md +1 -1
- package/rules/common/research.md +1 -1
- package/rules/common/review-before-change.md +1 -1
- package/scripts/sync-specialists.mjs +11 -8
- package/skills/docs/adr-workflow.md +3 -3
- package/skills/docs/init-docs.md +9 -9
- package/skills/docs/prd-workflow.md +5 -5
- package/skills/docs/product-intelligence-review.md +1 -1
- package/skills/docs/product-intelligence-workflow.md +2 -2
- package/skills/docs/product-signal-workflow.md +1 -1
- package/skills/docs/runbook-workflow.md +4 -4
- package/skills/exploration/unknown-codebase-onboarding.md +1 -1
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/routing.md +3 -3
- package/specialists/prompts/cx-architect.md +1 -1
- package/specialists/prompts/cx-docs-keeper.md +1 -1
- package/specialists/prompts/cx-researcher.md +1 -1
- package/specialists/prompts/cx-sre.md +1 -1
- package/specialists/role-manifests.json +6 -6
- package/templates/docs/README.md +125 -0
- package/templates/docs/construct_guide.md +3 -4
- package/templates/docs/persona-artifact.md +1 -1
- package/templates/docs/prds/README.md +25 -0
- package/templates/docs/prds/templates/_template.md +206 -0
- package/templates/docs/prds/templates/meta-prd.template.md +177 -0
- package/templates/docs/prds/templates/prd-business.template.md +61 -0
- package/templates/docs/prds/templates/prd-platform.template.md +81 -0
- package/templates/docs/prds/templates/prfaq.template.md +38 -0
- package/templates/docs/rfcs/README.md +22 -0
- package/templates/docs/rfcs/templates/_template.md +58 -0
- package/templates/docs/rfcs/templates/rfc-platform.template.md +72 -0
- package/templates/homebrew/construct.rb +1 -1
- package/templates/workflows/new-feature.yml +8 -8
- package/lib/intake/legacy-paths.mjs +0 -5
|
@@ -18,6 +18,7 @@ import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
|
18
18
|
import { join } from 'node:path';
|
|
19
19
|
import { homedir } from 'node:os';
|
|
20
20
|
import { spawnSync } from 'node:child_process';
|
|
21
|
+
import { configDir } from '../config/xdg.mjs';
|
|
21
22
|
|
|
22
23
|
// ── Multi-method auth helpers ────────────────────────────────────────────
|
|
23
24
|
|
|
@@ -268,8 +269,8 @@ export async function createJiraTicket(packet, { host, email, token, project, is
|
|
|
268
269
|
const resolvedToken = token || auth.token || process.env.JIRA_API_TOKEN;
|
|
269
270
|
const resolvedProject = project || auth.project || process.env.JIRA_PROJECT;
|
|
270
271
|
|
|
271
|
-
if (!resolvedHost) return { ok: false, externalUrl: null, externalId: null, error: 'JIRA_HOST not set (e.g. https://your-domain.atlassian.net). Check ~/.construct/config.env or shell rc.' };
|
|
272
|
-
if (!resolvedEmail || !resolvedToken) return { ok: false, externalUrl: null, externalId: null, error: 'JIRA_USER and JIRA_API_TOKEN required. Check ~/.construct/config.env, ~/.env, or 1Password.' };
|
|
272
|
+
if (!resolvedHost) return { ok: false, externalUrl: null, externalId: null, error: 'JIRA_HOST not set (e.g. https://your-domain.atlassian.net). Check ~/.config/construct/config.env or shell rc.' };
|
|
273
|
+
if (!resolvedEmail || !resolvedToken) return { ok: false, externalUrl: null, externalId: null, error: 'JIRA_USER and JIRA_API_TOKEN required. Check ~/.config/construct/config.env, ~/.env, or 1Password.' };
|
|
273
274
|
if (!resolvedProject) return { ok: false, externalUrl: null, externalId: null, error: 'JIRA_PROJECT not set (e.g. PROJ)' };
|
|
274
275
|
|
|
275
276
|
const t = packet?.triage || {};
|
|
@@ -385,8 +386,8 @@ export async function publishArtifactToConfluence(artifact, { host, email, token
|
|
|
385
386
|
const resolvedToken = token || auth.token || process.env.CONFLUENCE_API_TOKEN;
|
|
386
387
|
const resolvedSpace = space || auth.space || process.env.CONFLUENCE_SPACE;
|
|
387
388
|
|
|
388
|
-
if (!resolvedHost) return { ok: false, externalUrl: null, externalId: null, error: 'CONFLUENCE_HOST not set (e.g. https://your-domain.atlassian.net/wiki). Check ~/.construct/config.env or shell rc.' };
|
|
389
|
-
if (!resolvedEmail || !resolvedToken) return { ok: false, externalUrl: null, externalId: null, error: 'CONFLUENCE_USER and CONFLUENCE_API_TOKEN required. Check ~/.construct/config.env, ~/.env, or 1Password.' };
|
|
389
|
+
if (!resolvedHost) return { ok: false, externalUrl: null, externalId: null, error: 'CONFLUENCE_HOST not set (e.g. https://your-domain.atlassian.net/wiki). Check ~/.config/construct/config.env or shell rc.' };
|
|
390
|
+
if (!resolvedEmail || !resolvedToken) return { ok: false, externalUrl: null, externalId: null, error: 'CONFLUENCE_USER and CONFLUENCE_API_TOKEN required. Check ~/.config/construct/config.env, ~/.env, or 1Password.' };
|
|
390
391
|
if (!resolvedSpace) return { ok: false, externalUrl: null, externalId: null, error: 'CONFLUENCE_SPACE not set (e.g. PROD)' };
|
|
391
392
|
|
|
392
393
|
const prefix = { prd: 'PRD', adr: 'ADR', rfc: 'RFC' }[artifact.type] || artifact.type.toUpperCase();
|
|
@@ -608,7 +609,7 @@ function resolveCredentialSync(varName, homeDir) {
|
|
|
608
609
|
}
|
|
609
610
|
} catch { /* skip */ }
|
|
610
611
|
try {
|
|
611
|
-
const cfgPath = join(homeDir, '
|
|
612
|
+
const cfgPath = join(configDir(homeDir), 'config.env');
|
|
612
613
|
if (existsSync(cfgPath)) {
|
|
613
614
|
const content = readFileSync(cfgPath, 'utf8');
|
|
614
615
|
const m = content.match(new RegExp(`^${varName}=(.+)$`, 'm'));
|
|
@@ -703,9 +704,9 @@ function resolveCredential(varName, homeDir) {
|
|
|
703
704
|
}
|
|
704
705
|
} catch { /* skip */ }
|
|
705
706
|
|
|
706
|
-
//
|
|
707
|
+
// config.env in the XDG config dir
|
|
707
708
|
try {
|
|
708
|
-
const cfgPath = join(homeDir, '
|
|
709
|
+
const cfgPath = join(configDir(homeDir), 'config.env');
|
|
709
710
|
if (existsSync(cfgPath)) {
|
|
710
711
|
const content = readFileSync(cfgPath, 'utf8');
|
|
711
712
|
const m = content.match(new RegExp(`^${varName}=(.+)$`, 'm'));
|
package/lib/knowledge/rag.mjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Sources indexed:
|
|
9
9
|
* - Observations (.cx/observations/)
|
|
10
|
-
* - Artifacts (docs/adr/, docs/prd/, docs/rfc/)
|
|
10
|
+
* - Artifacts (docs/decisions/adr/, docs/specs/prd/, docs/decisions/rfc/)
|
|
11
11
|
* - Snapshots (.cx/snapshot.md + any configured output paths)
|
|
12
12
|
* - Approval queue (.cx/approval-queue.jsonl)
|
|
13
13
|
*
|
|
@@ -102,7 +102,7 @@ function loadMarkdownChunks(dir, source) {
|
|
|
102
102
|
*/
|
|
103
103
|
function loadArtifactChunks(rootDir) {
|
|
104
104
|
const chunks = [];
|
|
105
|
-
for (const subdir of ['docs/adr', 'docs/prd', 'docs/rfc', 'docs/concepts/architecture.md']) {
|
|
105
|
+
for (const subdir of ['docs/adr', 'docs/prd', 'docs/rfc', 'docs/guides/concepts/architecture.md']) {
|
|
106
106
|
const full = path.resolve(rootDir, subdir);
|
|
107
107
|
if (subdir.endsWith('.md')) {
|
|
108
108
|
if (!fs.existsSync(full)) continue;
|
package/lib/knowledge/search.mjs
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* `knowledge_search` MCP tool — no daemon, no network, no external deps.
|
|
7
7
|
*
|
|
8
8
|
* Sources searched (in priority order):
|
|
9
|
-
* 1. docs/concepts/*.md — architecture, agents, gates, durable state
|
|
10
|
-
* 2. docs/start/*.mdx — install + first task
|
|
9
|
+
* 1. docs/guides/concepts/*.md — architecture, agents, gates, durable state
|
|
10
|
+
* 2. docs/guides/start/*.mdx — install + first task
|
|
11
11
|
* 3. docs/README.md — docs index + contract
|
|
12
12
|
* 4. .cx/knowledge/ — operator-written internal docs
|
|
13
|
-
* 5. Any *.md in docs/cookbook/ — task-oriented recipes
|
|
13
|
+
* 5. Any *.md in docs/guides/cookbook/ — task-oriented recipes
|
|
14
14
|
* 6. <projectRoot>/.cx/knowledge/** — the cwd project's knowledge tree,
|
|
15
15
|
* including `external/research/` written by `construct knowledge add`,
|
|
16
16
|
* so foreign-project queries surface project content alongside bundled docs.
|
|
@@ -47,17 +47,17 @@ function buildSourceList(repoRoot = REPO_ROOT, projectRoot = null) {
|
|
|
47
47
|
const sources = [];
|
|
48
48
|
|
|
49
49
|
const priority = [
|
|
50
|
-
'docs/concepts/architecture.md',
|
|
51
|
-
'docs/concepts/agents-and-personas.mdx',
|
|
52
|
-
'docs/concepts/gates-and-enforcement.md',
|
|
53
|
-
'docs/concepts/beads-and-state.md',
|
|
54
|
-
'docs/concepts/prompt-surfaces.md',
|
|
55
|
-
'docs/concepts/knowledge-layout.md',
|
|
56
|
-
'docs/concepts/deployment-model.md',
|
|
57
|
-
'docs/concepts/embedding-boundary.md',
|
|
50
|
+
'docs/guides/concepts/architecture.md',
|
|
51
|
+
'docs/guides/concepts/agents-and-personas.mdx',
|
|
52
|
+
'docs/guides/concepts/gates-and-enforcement.md',
|
|
53
|
+
'docs/guides/concepts/beads-and-state.md',
|
|
54
|
+
'docs/guides/concepts/prompt-surfaces.md',
|
|
55
|
+
'docs/guides/concepts/knowledge-layout.md',
|
|
56
|
+
'docs/guides/concepts/deployment-model.md',
|
|
57
|
+
'docs/guides/concepts/embedding-boundary.md',
|
|
58
58
|
'docs/README.md',
|
|
59
|
-
'docs/start/install.mdx',
|
|
60
|
-
'docs/start/first-task.mdx',
|
|
59
|
+
'docs/guides/start/install.mdx',
|
|
60
|
+
'docs/guides/start/first-task.mdx',
|
|
61
61
|
];
|
|
62
62
|
|
|
63
63
|
for (const rel of priority) {
|
|
@@ -66,12 +66,12 @@ function buildSourceList(repoRoot = REPO_ROOT, projectRoot = null) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// Cookbook recipes (task-oriented how-tos)
|
|
69
|
-
const cookbookDir = join(repoRoot, 'docs', 'cookbook');
|
|
69
|
+
const cookbookDir = join(repoRoot, 'docs', 'guides', 'cookbook');
|
|
70
70
|
if (existsSync(cookbookDir)) {
|
|
71
71
|
for (const file of readdirSync(cookbookDir)) {
|
|
72
72
|
if (file.endsWith('.md') || file.endsWith('.mdx')) {
|
|
73
73
|
const full = join(cookbookDir, file);
|
|
74
|
-
sources.push({ path: full, rel: `docs/cookbook/${file}`, priority: 2 });
|
|
74
|
+
sources.push({ path: full, rel: `docs/guides/cookbook/${file}`, priority: 2 });
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* Triggered from two entry points:
|
|
11
11
|
* - `construct cleanup` — manual sweep, optionally --dry-run
|
|
12
12
|
* - bin/construct startup check — automatic when the installed version
|
|
13
|
-
* differs from
|
|
14
|
-
* so an upgrade reclaims
|
|
13
|
+
* differs from the XDG state-dir
|
|
14
|
+
* .cleanup-stamp, so an upgrade reclaims
|
|
15
|
+
* disk without
|
|
15
16
|
* requiring a manual command.
|
|
16
17
|
*
|
|
17
18
|
* Scope is deliberately narrow: only artifacts whose retention is owned by
|
|
@@ -24,6 +25,9 @@ import fs from 'node:fs';
|
|
|
24
25
|
import os from 'node:os';
|
|
25
26
|
import path from 'node:path';
|
|
26
27
|
|
|
28
|
+
import { stateDir, cacheDir } from '../config/xdg.mjs';
|
|
29
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
30
|
+
|
|
27
31
|
const HARD_BUDGET_MS = 5000;
|
|
28
32
|
|
|
29
33
|
// Size caps in bytes. Each entry: [filename relative to ~/.cx/, maxBytes].
|
|
@@ -92,7 +96,7 @@ function rotationConfig(env = process.env) {
|
|
|
92
96
|
* were oversized before the cap existed.
|
|
93
97
|
*/
|
|
94
98
|
export function cleanupEmbedLog({ homeDir = os.homedir(), env = process.env, dryRun = false } = {}) {
|
|
95
|
-
const dir = path.join(homeDir, '
|
|
99
|
+
const dir = path.join(doctorRoot(homeDir), 'runtime');
|
|
96
100
|
const baseLog = path.join(dir, 'embed-daemon.log');
|
|
97
101
|
const { maxBytes, keep } = rotationConfig(env);
|
|
98
102
|
|
|
@@ -149,7 +153,7 @@ export function cleanupEmbedLog({ homeDir = os.homedir(), env = process.env, dry
|
|
|
149
153
|
* recent records survive.
|
|
150
154
|
*/
|
|
151
155
|
export function cleanupJsonlLogs({ homeDir = os.homedir(), dryRun = false } = {}) {
|
|
152
|
-
const dir =
|
|
156
|
+
const dir = doctorRoot(homeDir);
|
|
153
157
|
const summary = { truncated: [], freedBytes: 0 };
|
|
154
158
|
if (!fs.existsSync(dir)) return summary;
|
|
155
159
|
|
|
@@ -168,11 +172,11 @@ export function cleanupJsonlLogs({ homeDir = os.homedir(), dryRun = false } = {}
|
|
|
168
172
|
}
|
|
169
173
|
|
|
170
174
|
/**
|
|
171
|
-
* Age out
|
|
175
|
+
* Age out XDG cache-dir entries older than `maxAgeDays`. Cache hits are
|
|
172
176
|
* idempotent and re-fetched on demand, so pruning is safe.
|
|
173
177
|
*/
|
|
174
178
|
export function cleanupCacheDir({ homeDir = os.homedir(), maxAgeDays = 30, dryRun = false } = {}) {
|
|
175
|
-
const dir =
|
|
179
|
+
const dir = cacheDir(homeDir);
|
|
176
180
|
const summary = { removed: [], freedBytes: 0 };
|
|
177
181
|
if (!fs.existsSync(dir)) return summary;
|
|
178
182
|
|
|
@@ -249,18 +253,18 @@ export function formatBytes(bytes) {
|
|
|
249
253
|
// ---------------------------------------------------------------------------
|
|
250
254
|
// Version stamp — drives automatic cleanup on upgrade.
|
|
251
255
|
//
|
|
252
|
-
//
|
|
253
|
-
// On every CLI invocation, bin/construct compares the current package
|
|
256
|
+
// The .cleanup-stamp in the XDG state dir records the last version that ran
|
|
257
|
+
// cleanup. On every CLI invocation, bin/construct compares the current package
|
|
254
258
|
// version to the stamp. A mismatch triggers a single cleanup pass and
|
|
255
259
|
// writes the new version into the stamp. The first ever invocation also
|
|
256
260
|
// triggers cleanup (no stamp present) — this is what unblocks existing
|
|
257
261
|
// installs that already have 34 GB logs sitting around.
|
|
258
262
|
// ---------------------------------------------------------------------------
|
|
259
263
|
|
|
260
|
-
const
|
|
264
|
+
const STAMP_FILENAME = '.cleanup-stamp';
|
|
261
265
|
|
|
262
266
|
export function stampPath(homeDir = os.homedir()) {
|
|
263
|
-
return path.join(homeDir,
|
|
267
|
+
return path.join(stateDir(homeDir), STAMP_FILENAME);
|
|
264
268
|
}
|
|
265
269
|
|
|
266
270
|
export function readVersionStamp({ homeDir = os.homedir() } = {}) {
|
package/lib/mcp/server.mjs
CHANGED
|
@@ -859,7 +859,7 @@ const ALL_TOOL_DEFS = [
|
|
|
859
859
|
},
|
|
860
860
|
{
|
|
861
861
|
name: 'profile_create',
|
|
862
|
-
description: 'Scaffold a draft org profile under `.cx/profiles/draft-<id>/` (requirements.md + profile.json + persona stubs + department charters). Writes durable state — requires `confirm=true`. For curated catalog work, follow `docs/concepts/profile-lifecycle.md` after creation.',
|
|
862
|
+
description: 'Scaffold a draft org profile under `.cx/profiles/draft-<id>/` (requirements.md + profile.json + persona stubs + department charters). Writes durable state — requires `confirm=true`. For curated catalog work, follow `docs/guides/concepts/profile-lifecycle.md` after creation.',
|
|
863
863
|
inputSchema: {
|
|
864
864
|
type: 'object',
|
|
865
865
|
required: ['confirm', 'id'],
|
|
@@ -13,6 +13,7 @@ import { inspectContextState } from '../../context-state.mjs';
|
|
|
13
13
|
import { loadWorkflow, summarizeWorkflow, inspectWorkflowHealth } from '../../workflow-state.mjs';
|
|
14
14
|
import { readCurrentModels, resolveExecutionContractModelMetadata, selectModelTierForWorkCategory } from '../../model-router.mjs';
|
|
15
15
|
import { buildPublicHealthSurface } from '../../status.mjs';
|
|
16
|
+
import { doctorRoot } from '../../config/xdg.mjs';
|
|
16
17
|
|
|
17
18
|
export function exec(cmd, cwd) {
|
|
18
19
|
return execSync(cmd, { stdio: 'pipe', timeout: 10000, cwd }).toString().trim();
|
|
@@ -60,7 +61,7 @@ function isBinary(buffer) {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export function agentHealth(args) {
|
|
63
|
-
const reviewDir = join(
|
|
64
|
+
const reviewDir = join(doctorRoot(), 'performance-reviews');
|
|
64
65
|
|
|
65
66
|
if (!existsSync(reviewDir)) {
|
|
66
67
|
return { error: "No performance reviews found. Run 'construct review' to generate one." };
|
|
@@ -21,10 +21,11 @@ import os from 'node:os';
|
|
|
21
21
|
import path from 'node:path';
|
|
22
22
|
import { getProviderModelCatalog, PROVIDER_FAMILY_TIERS } from './model-router.mjs';
|
|
23
23
|
import { getPricingForModels, formatPricingLabel } from './model-pricing.mjs';
|
|
24
|
+
import { configDir } from './config/xdg.mjs';
|
|
24
25
|
|
|
25
26
|
const CHEAPEST_PREF_KEY = 'CHEAPEST_PROVIDER_ENABLED';
|
|
26
27
|
const CHEAPEST_CHECKED_KEY = 'CHEAPEST_PROVIDER_CHECKED';
|
|
27
|
-
const ENV_PATH = path.join(
|
|
28
|
+
const ENV_PATH = path.join(configDir(), 'config.env');
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Resolve the cheapest configured provider for a given tier.
|
|
@@ -32,7 +33,7 @@ const ENV_PATH = path.join(os.homedir(), '.construct', 'config.env');
|
|
|
32
33
|
* @param {string} tier - One of "reasoning", "standard", "fast"
|
|
33
34
|
* @param {object} [opts]
|
|
34
35
|
* @param {object} [opts.env] - Environment object (default: process.env)
|
|
35
|
-
* @param {string} [opts.envPath] - Path to config.env (default:
|
|
36
|
+
* @param {string} [opts.envPath] - Path to config.env (default: config.env in the XDG config dir)
|
|
36
37
|
* @param {Function} [opts.getPricingForModels] - Injectable pricing fetcher
|
|
37
38
|
* @returns {Promise<{
|
|
38
39
|
* providerId: string|null,
|
|
@@ -130,7 +131,7 @@ export async function rankConfiguredProvidersByCost(tier, opts = {}) {
|
|
|
130
131
|
/**
|
|
131
132
|
* Check if the user has opted into cheapest-provider selection.
|
|
132
133
|
*
|
|
133
|
-
* @param {string} [envPath] - Path to config.env (default:
|
|
134
|
+
* @param {string} [envPath] - Path to config.env (default: config.env in the XDG config dir)
|
|
134
135
|
* @param {object} [opts.env] - Environment override
|
|
135
136
|
* @returns {boolean}
|
|
136
137
|
*/
|
package/lib/model-pricing.mjs
CHANGED
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import fs from 'node:fs';
|
|
19
|
-
import os from 'node:os';
|
|
20
19
|
import path from 'node:path';
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
import { doctorRoot } from './config/xdg.mjs';
|
|
22
|
+
|
|
23
|
+
const DEFAULT_CACHE_FILE = path.join(doctorRoot(), 'model-pricing.json');
|
|
23
24
|
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
24
25
|
const OPENROUTER_ENDPOINT = 'https://openrouter.ai/api/v1/models';
|
|
25
26
|
|
package/lib/model-router.mjs
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
loadModelsCatalogContext,
|
|
42
42
|
mergeLiveModelsIntoProviders,
|
|
43
43
|
} from "./models/catalog.mjs";
|
|
44
|
+
import { configDir } from "./config/xdg.mjs";
|
|
44
45
|
|
|
45
46
|
function uniqueStrings(values = []) {
|
|
46
47
|
return [...new Set(values.filter((value) => typeof value === "string" && value.trim()))];
|
|
@@ -380,7 +381,7 @@ function hasCopilotCredential() {
|
|
|
380
381
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
381
382
|
if (!home) return false;
|
|
382
383
|
const candidates = [
|
|
383
|
-
path.join(home, '
|
|
384
|
+
path.join(configDir(home), 'auth', 'github-copilot.json'),
|
|
384
385
|
path.join(home, '.config', 'github-copilot', 'apps.json'),
|
|
385
386
|
path.join(home, '.config', 'github-copilot', 'hosts.json'),
|
|
386
387
|
];
|
|
@@ -421,7 +422,7 @@ function isProviderConfigured(familyId, env, { allowAmbient = true } = {}) {
|
|
|
421
422
|
} catch { /* not available */ }
|
|
422
423
|
}
|
|
423
424
|
|
|
424
|
-
// Any credential source counts — env,
|
|
425
|
+
// Any credential source counts — env, the XDG config dir config.env, ~/.env, the
|
|
425
426
|
// project .env, and shell rc exports — and a stored 1Password op:// reference
|
|
426
427
|
// counts as configured because the resolver reads it lazily at call time.
|
|
427
428
|
if (hasAnySecret(varNames, { env, allowAmbient })) return true;
|
|
@@ -569,7 +570,7 @@ function availabilityNotice(rejected) {
|
|
|
569
570
|
if (rejected.reason === 'provider_not_configured') {
|
|
570
571
|
const family = rejected.provider || '';
|
|
571
572
|
if (family.startsWith('openrouter')) {
|
|
572
|
-
return `Saved ${label} — OpenRouter not configured. Set OPENROUTER_API_KEY in ~/.construct/config.env or run \`construct creds set openrouter\`.`;
|
|
573
|
+
return `Saved ${label} — OpenRouter not configured. Set OPENROUTER_API_KEY in ~/.config/construct/config.env or run \`construct creds set openrouter\`.`;
|
|
573
574
|
}
|
|
574
575
|
return `Saved ${label} — provider not configured.`;
|
|
575
576
|
}
|
package/lib/models/catalog.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import os from 'node:os';
|
|
|
12
12
|
import { pollFreeModels } from '../model-free-selector.mjs';
|
|
13
13
|
import { resolveFirstSecret } from '../providers/secret-resolver.mjs';
|
|
14
14
|
import { loadProjectConfig } from '../config/project-config.mjs';
|
|
15
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
15
16
|
|
|
16
17
|
export const MODEL_VISIBILITY_MODES = ['all_configured', 'tier_defaults', 'explicit'];
|
|
17
18
|
|
|
@@ -36,7 +37,7 @@ function uniqueStrings(values = []) {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
function cachePath(homeDir = os.homedir()) {
|
|
39
|
-
return path.join(homeDir,
|
|
40
|
+
return path.join(doctorRoot(homeDir), CACHE_FILENAME);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export function resolveModelsConfig(projectConfig = {}) {
|
|
@@ -32,6 +32,7 @@ import { getModelVerdict } from '../ollama/capability-store.mjs';
|
|
|
32
32
|
import { isLocalModel } from '../mcp/tool-budget.mjs';
|
|
33
33
|
import { transportForProviderGroup, classifyCapabilityClass } from './behavior-matrix.mjs';
|
|
34
34
|
import { resolveCapabilityTier, resolveModelOperatingProfile } from '../model-router.mjs';
|
|
35
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
35
36
|
|
|
36
37
|
export const EXECUTION_PROFILE_SCHEMA_VERSION = 1;
|
|
37
38
|
|
|
@@ -81,7 +82,7 @@ function hasVendorAdapter(modelId) {
|
|
|
81
82
|
|
|
82
83
|
function loadOverridesFile(homeDir) {
|
|
83
84
|
try {
|
|
84
|
-
const raw = JSON.parse(readFileSync(join(homeDir,
|
|
85
|
+
const raw = JSON.parse(readFileSync(join(doctorRoot(homeDir), OVERRIDES_FILENAME), 'utf8'));
|
|
85
86
|
if (raw && typeof raw === 'object' && raw.models && typeof raw.models === 'object') return raw.models;
|
|
86
87
|
} catch { /* absent or malformed — no overrides */ }
|
|
87
88
|
return {};
|
|
@@ -18,6 +18,7 @@ import { resolveFirstSecret } from '../providers/secret-resolver.mjs';
|
|
|
18
18
|
import { getProviderModelCatalog } from '../model-router.mjs';
|
|
19
19
|
import { listInstalledOllamaModels, toOllamaNativeModelId } from '../ollama/installed-models.mjs';
|
|
20
20
|
import { getPricingForModels } from '../model-pricing.mjs';
|
|
21
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
21
22
|
|
|
22
23
|
const FETCH_TIMEOUT_MS = 10_000;
|
|
23
24
|
const CACHE_TTL_MS = 10 * 60 * 1000;
|
|
@@ -37,7 +38,7 @@ const PROVIDER_LABELS = {
|
|
|
37
38
|
const PROVIDER_ORDER = ['anthropic', 'openai', 'github-copilot', 'openrouter', 'ollama', 'local'];
|
|
38
39
|
|
|
39
40
|
function cachePath(homeDir = os.homedir()) {
|
|
40
|
-
return path.join(homeDir,
|
|
41
|
+
return path.join(doctorRoot(homeDir), CACHE_FILENAME);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
async function fetchJson(url, { headers = {}, signal } = {}) {
|
package/lib/ollama-manager.mjs
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
import { execFileSync } from 'node:child_process';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
|
-
import { homedir } from 'node:os';
|
|
12
11
|
|
|
13
12
|
// argv-array form for every shell-out: no shell interpolation, model names and
|
|
14
13
|
// JSON bodies are passed as discrete arguments / stdin so user-supplied values
|
package/lib/op-log.mjs
CHANGED
|
@@ -22,12 +22,13 @@ import path from 'node:path';
|
|
|
22
22
|
import os from 'node:os';
|
|
23
23
|
|
|
24
24
|
import { makeLogger, newRequestId } from './logger.mjs';
|
|
25
|
+
import { doctorRoot } from './config/xdg.mjs';
|
|
25
26
|
|
|
26
27
|
const NOOP_LOGGER = Object.freeze({ debug() {}, info() {}, warn() {}, error() {} });
|
|
27
28
|
|
|
28
29
|
export function startOpLog(op, { homeDir = os.homedir(), env = process.env, now = new Date() } = {}) {
|
|
29
30
|
const id = newRequestId();
|
|
30
|
-
const dir =
|
|
31
|
+
const dir = doctorRoot(homeDir);
|
|
31
32
|
const stamp = now.toISOString().replace(/[:.]/g, '-');
|
|
32
33
|
const logPath = path.join(dir, `${op}-${stamp}.log`);
|
|
33
34
|
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
import { syncModelPricing, refreshPricingCatalog } from "./telemetry/model-pricing-catalog.mjs";
|
|
29
29
|
import { estimateUsageCost } from "./telemetry/model-pricing-catalog.mjs";
|
|
30
30
|
import { buildSessionPrelude } from "./intake/session-prelude.mjs";
|
|
31
|
+
import { doctorRoot } from './config/xdg.mjs';
|
|
31
32
|
|
|
32
33
|
const EFFICIENCY_SESSION_IDLE_RESET_MS = 2 * 60 * 60 * 1000;
|
|
33
34
|
const EFFICIENCY_REPEATED_READ_THRESHOLD = 5;
|
|
@@ -94,7 +95,7 @@ const COOLDOWN_MS = 10 * 60 * 1000;
|
|
|
94
95
|
|
|
95
96
|
function getStatePath(env = process.env) {
|
|
96
97
|
const home = env.HOME || homedir();
|
|
97
|
-
return join(home, "
|
|
98
|
+
return join(doctorRoot(home), "construct-opencode-fallback.json");
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
function flatten(value) {
|
|
@@ -520,7 +521,7 @@ export function buildRuntimeTracePayload(event, { env = process.env } = {}) {
|
|
|
520
521
|
|
|
521
522
|
function efficiencyStorePath(env = process.env) {
|
|
522
523
|
const home = env.HOME || homedir();
|
|
523
|
-
return join(home, "
|
|
524
|
+
return join(doctorRoot(home), "session-efficiency.json");
|
|
524
525
|
}
|
|
525
526
|
|
|
526
527
|
function loadEfficiencyStats(nowIso, env = process.env) {
|
|
@@ -50,8 +50,11 @@ function collectBypassArtifacts(projectDir) {
|
|
|
50
50
|
|
|
51
51
|
function collectReviewerGaps(projectDir) {
|
|
52
52
|
const roots = [
|
|
53
|
+
path.join(projectDir, 'docs', 'specs', 'prd'),
|
|
53
54
|
path.join(projectDir, 'docs', 'prd'),
|
|
55
|
+
path.join(projectDir, 'docs', 'decisions', 'adr'),
|
|
54
56
|
path.join(projectDir, 'docs', 'adr'),
|
|
57
|
+
path.join(projectDir, 'docs', 'decisions', 'rfc'),
|
|
55
58
|
path.join(projectDir, 'docs', 'rfc'),
|
|
56
59
|
];
|
|
57
60
|
const gaps = [];
|
package/lib/oracle/cli.mjs
CHANGED
|
@@ -20,11 +20,12 @@ import {
|
|
|
20
20
|
import { runOracleTick, listPending, approvePending, defaultRootDir } from './actions.mjs';
|
|
21
21
|
import { collectOracleGaps, formatOracleGapsReport } from './gaps.mjs';
|
|
22
22
|
import { reconcileOracleHygieneBeads } from './reconcile.mjs';
|
|
23
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
23
24
|
|
|
24
25
|
const MODULE_FILE = fileURLToPath(import.meta.url);
|
|
25
26
|
|
|
26
27
|
function runtimeDir(homeDir) {
|
|
27
|
-
return path.join(homeDir, '
|
|
28
|
+
return path.join(doctorRoot(homeDir), 'runtime', 'oracle');
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
async function cmdStart(args, { rootDir, projectDir, homeDir }) {
|
package/lib/oracle/execute.mjs
CHANGED
|
@@ -16,9 +16,10 @@ import { recordAndMaybeInvoke } from '../roles/gateway.mjs';
|
|
|
16
16
|
import { validateCapabilityRegistry } from '../registry/validate.mjs';
|
|
17
17
|
import { writeRoutingArtifact } from './dispatch.mjs';
|
|
18
18
|
import { routeAction } from './routing.mjs';
|
|
19
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
19
20
|
|
|
20
21
|
function rolePendingPath(homeDir = homedir()) {
|
|
21
|
-
const root = process.env.CONSTRUCT_ROLES_ROOT ||
|
|
22
|
+
const root = process.env.CONSTRUCT_ROLES_ROOT || doctorRoot(homeDir);
|
|
22
23
|
return path.join(root, 'role-pending.jsonl');
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -40,7 +41,7 @@ async function dispatchSpecialist({ action, projectDir, homeDir, personaId, even
|
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
const prevRolesRoot = process.env.CONSTRUCT_ROLES_ROOT;
|
|
43
|
-
process.env.CONSTRUCT_ROLES_ROOT =
|
|
44
|
+
process.env.CONSTRUCT_ROLES_ROOT = doctorRoot(homeDir);
|
|
44
45
|
|
|
45
46
|
const event = {
|
|
46
47
|
project: projectDir,
|
package/lib/oracle/index.mjs
CHANGED
|
@@ -13,11 +13,12 @@ import { homedir } from 'node:os';
|
|
|
13
13
|
import { createDaemon } from '../daemons/contract.mjs';
|
|
14
14
|
import { memoryCapMbFor } from '../resources/process-budget.mjs';
|
|
15
15
|
import { runOracleTick, defaultRootDir } from './actions.mjs';
|
|
16
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
16
17
|
|
|
17
18
|
export const KILLSWITCH_ENV = 'CONSTRUCT_ORACLE';
|
|
18
19
|
|
|
19
20
|
function runtimeDir(homeDir = homedir()) {
|
|
20
|
-
return path.join(homeDir, '
|
|
21
|
+
return path.join(doctorRoot(homeDir), 'runtime', 'oracle');
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export function heartbeatPath(homeDir = homedir()) {
|
|
@@ -21,6 +21,7 @@ import { collectArtifactGateSignals } from './artifact-gate.mjs';
|
|
|
21
21
|
import { loadGraph, nodesByType, dependentsOf } from '../graph/store.mjs';
|
|
22
22
|
import { hashFiles } from '../graph/build-from-registry.mjs';
|
|
23
23
|
import { readViolationSupersedeCutoff } from '../contracts/violation-log.mjs';
|
|
24
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
24
25
|
|
|
25
26
|
const RECENT_MS = 24 * 60 * 60 * 1000;
|
|
26
27
|
const DOCTOR_LIMIT = 50;
|
|
@@ -128,7 +129,7 @@ function collectContractViolations(projectDir) {
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
function collectDoctorLog(homeDir) {
|
|
131
|
-
const file = path.join(homeDir, '
|
|
132
|
+
const file = path.join(doctorRoot(homeDir), 'doctor-log.jsonl');
|
|
132
133
|
const since = Date.now() - RECENT_MS;
|
|
133
134
|
const recent = readRecentJsonl(file, { since, limit: DOCTOR_LIMIT });
|
|
134
135
|
return {
|
|
@@ -205,7 +206,7 @@ function collectBeadsOpenCount(projectDir) {
|
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
function collectHookFailures(homeDir) {
|
|
208
|
-
const file = path.join(homeDir, '
|
|
209
|
+
const file = path.join(doctorRoot(homeDir), 'doctor-log.jsonl');
|
|
209
210
|
const since = Date.now() - RECENT_MS;
|
|
210
211
|
const recent = readRecentJsonl(file, { since, limit: DOCTOR_LIMIT });
|
|
211
212
|
const failures = recent.filter((r) =>
|
|
@@ -24,11 +24,12 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import fs from 'node:fs';
|
|
26
26
|
import path from 'node:path';
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
28
29
|
|
|
29
30
|
const WINDOW_DAYS = 7;
|
|
30
|
-
const REVIEW_DIR = path.join(
|
|
31
|
-
const COST_LEDGER = path.join(
|
|
31
|
+
const REVIEW_DIR = path.join(doctorRoot(), 'performance-reviews');
|
|
32
|
+
const COST_LEDGER = path.join(doctorRoot(), 'session-cost.jsonl');
|
|
32
33
|
|
|
33
34
|
function quantile(values, q) {
|
|
34
35
|
if (!values.length) return null;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* local-model provisioning, instructions-only). The host enumeration, the
|
|
7
7
|
* displayName-to-id map, the hasNativeSubagents matrix, and the global hook /
|
|
8
8
|
* MCP allowlists all derive from this one registry so sync and init read host
|
|
9
|
-
* capabilities as data. See docs/adr/0033-platform-capability-registry.md.
|
|
9
|
+
* capabilities as data. See docs/decisions/adr/0033-platform-capability-registry.md.
|
|
10
10
|
*
|
|
11
11
|
* Hand-rolled validation (no AJV/zod) so this loads dependency-free on the
|
|
12
12
|
* bootstrap paths that run before npm install — same constraint as
|
package/lib/plugin-registry.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from
|
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { dirname, extname, join, resolve } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { configDir } from "./config/xdg.mjs";
|
|
11
12
|
|
|
12
13
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
export const MANIFEST_VERSION = 1;
|
|
@@ -123,7 +124,7 @@ export function resolvePluginDirs({ cwd = process.cwd(), homeDir = homedir(), en
|
|
|
123
124
|
return [...new Set([
|
|
124
125
|
join(cwd, ".cx", "plugins"),
|
|
125
126
|
join(cwd, ".construct", "plugins"),
|
|
126
|
-
join(homeDir, "
|
|
127
|
+
join(configDir(homeDir), "plugins"),
|
|
127
128
|
...extra,
|
|
128
129
|
].map((dir) => resolve(dir)))];
|
|
129
130
|
}
|
|
@@ -41,7 +41,7 @@ function archiveDir(cwd, id) {
|
|
|
41
41
|
|
|
42
42
|
const REQUIREMENTS_BRIEF = (id, displayName) => `# Profile requirements: ${displayName} (${id})
|
|
43
43
|
|
|
44
|
-
This brief is a discovery and design contract for a new Construct profile. Methodology lives in \`docs/concepts/persona-research.md\` and lifecycle in \`docs/concepts/profile-lifecycle.md\`. Treat each section as a task to dispatch to the named specialist. A draft profile is not ready to promote until every section has evidence backing it. The aim is research-grounded, not opinion-grounded.
|
|
44
|
+
This brief is a discovery and design contract for a new Construct profile. Methodology lives in \`docs/guides/concepts/persona-research.md\` and lifecycle in \`docs/guides/concepts/profile-lifecycle.md\`. Treat each section as a task to dispatch to the named specialist. A draft profile is not ready to promote until every section has evidence backing it. The aim is research-grounded, not opinion-grounded.
|
|
45
45
|
|
|
46
46
|
Specialists used here are the existing Construct cx-* personas (already in the registry). They are dispatched the standard way; this doc just names which questions each one owns.
|
|
47
47
|
|
|
@@ -145,7 +145,7 @@ const DRAFT_PROFILE_TEMPLATE = (id, displayName) => ({
|
|
|
145
145
|
function personaTemplate(displayName) {
|
|
146
146
|
return `# ${displayName}
|
|
147
147
|
|
|
148
|
-
> Persona research artifact. Methodology: \`docs/concepts/persona-research.md\`. Fill from evidence; opinion is rejected at review.
|
|
148
|
+
> Persona research artifact. Methodology: \`docs/guides/concepts/persona-research.md\`. Fill from evidence; opinion is rejected at review.
|
|
149
149
|
|
|
150
150
|
## Goals
|
|
151
151
|
- <What does success look like for this role?>
|
|
@@ -178,7 +178,7 @@ function personaTemplate(displayName) {
|
|
|
178
178
|
function departmentTemplate(id, displayName) {
|
|
179
179
|
return `# ${displayName} (${id})
|
|
180
180
|
|
|
181
|
-
> Department charter. One paragraph: what this department owns, what it does not own, who it hands off to. Frame from organizational-design research (\`docs/concepts/persona-research.md\` § departmental structure).
|
|
181
|
+
> Department charter. One paragraph: what this department owns, what it does not own, who it hands off to. Frame from organizational-design research (\`docs/guides/concepts/persona-research.md\` § departmental structure).
|
|
182
182
|
|
|
183
183
|
## Charter
|
|
184
184
|
|
package/lib/project-profile.mjs
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
16
16
|
import { join, resolve } from 'node:path';
|
|
17
|
+
import { configDir } from './config/xdg.mjs';
|
|
17
18
|
|
|
18
19
|
// Detection rules. Each rule checks a filesystem signal and emits one or more
|
|
19
20
|
// tags. Keep rules tight — false positives cascade into bad filter decisions.
|
|
@@ -430,7 +431,7 @@ export function enumerateInstalledSkills(homeDir = (process.env.HOME || process.
|
|
|
430
431
|
// Codex
|
|
431
432
|
join(homeDir, '.codex', 'skills'),
|
|
432
433
|
// Construct native
|
|
433
|
-
join(homeDir, '
|
|
434
|
+
join(configDir(homeDir), 'skills'),
|
|
434
435
|
];
|
|
435
436
|
for (const dir of candidates) {
|
|
436
437
|
try {
|