@geraldmaron/construct 1.2.0 → 1.2.2
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 +8 -7
- package/bin/construct +206 -62
- 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-manifest.mjs +150 -4
- package/lib/artifact-type-from-path.mjs +9 -5
- package/lib/artifact-workflow.mjs +252 -0
- package/lib/audit-specialists.mjs +1 -1
- package/lib/audit-trail.mjs +3 -3
- package/lib/auto-docs.mjs +13 -11
- 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/chat/present.mjs +1 -0
- package/lib/cli-commands.mjs +23 -6
- package/lib/cli-service-inventory.mjs +42 -0
- package/lib/comment-lint.mjs +9 -5
- package/lib/config/alias.mjs +3 -2
- package/lib/config/intake-policy.mjs +139 -0
- package/lib/config/project-config.mjs +106 -7
- package/lib/config/schema.mjs +45 -6
- package/lib/config/source-targets.mjs +311 -0
- 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/source-checkout.mjs +16 -0
- 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 +39 -17
- 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/auto-sources.mjs +44 -0
- package/lib/embed/cli.mjs +9 -6
- package/lib/embed/config.mjs +6 -5
- package/lib/embed/daemon.mjs +6 -5
- package/lib/embed/demand-fetch.mjs +50 -19
- package/lib/embed/inbox-live-watcher.mjs +1 -1
- package/lib/embed/inbox.mjs +52 -41
- 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/embed/worker.mjs +2 -1
- package/lib/embedded-contract/index.mjs +11 -0
- package/lib/engine/registry.mjs +3 -3
- package/lib/env-config.mjs +34 -12
- package/lib/export-branding.mjs +32 -0
- 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 +20 -16
- 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 +47 -92
- package/lib/init.mjs +7 -5
- package/lib/install/desktop-binary-download.mjs +5 -2
- package/lib/intake/constants.mjs +29 -0
- package/lib/intake/daemon.mjs +31 -8
- package/lib/intake/intake-config.mjs +32 -112
- 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 +20 -2
- package/lib/mcp/tools/embedded-contract.mjs +14 -0
- 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/playwright-demo.mjs +2 -0
- 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-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 +53 -26
- package/lib/server/insights.mjs +5 -4
- package/lib/server/rate-limit.mjs +5 -3
- package/lib/server/webhook.mjs +5 -3
- package/lib/service-manager.mjs +5 -3
- package/lib/setup.mjs +9 -8
- 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 +3 -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/artifact-manifest.json +11 -2
- package/specialists/artifact-manifest.schema.json +31 -1
- 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/distribution/construct-reference.docx +0 -0
- 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
|
@@ -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/playwright-demo.mjs
CHANGED
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 {
|
package/lib/project-root.mjs
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* Writers that own per-project state (contract violations, audit reads, agent
|
|
5
5
|
* dispatches, intent verifications) call `resolveProjectScope()` to decide
|
|
6
6
|
* which `<project>/.cx/<file>.jsonl` to write to. When no project is
|
|
7
|
-
* detected, the call returns `null` and writers fall back to the
|
|
8
|
-
* user-scope path (
|
|
7
|
+
* detected, the call returns `null` and writers fall back to the global
|
|
8
|
+
* user-scope path (`<doctorRoot>/<file>.jsonl`) so existing standalone hook
|
|
9
9
|
* invocations don't lose data.
|
|
10
10
|
*
|
|
11
11
|
* Writers that own cross-project telemetry (skill calls, role-pending,
|
|
12
|
-
* session cost) keep using
|
|
12
|
+
* session cost) keep using the global doctor root but tag each entry with the
|
|
13
13
|
* `projectId` from `resolveProjectId()` so a reader can attribute the
|
|
14
14
|
* entry to a specific project later.
|
|
15
15
|
*
|
|
@@ -24,6 +24,8 @@ import path from 'node:path';
|
|
|
24
24
|
import os from 'node:os';
|
|
25
25
|
import { createHash } from 'node:crypto';
|
|
26
26
|
|
|
27
|
+
import { doctorRoot } from './config/xdg.mjs';
|
|
28
|
+
|
|
27
29
|
const HOME = os.homedir();
|
|
28
30
|
const MARKERS = ['.cx', '.construct'];
|
|
29
31
|
const cache = new Map(); // cwd → { projectRoot, projectId, scope } | null
|
|
@@ -84,13 +86,13 @@ export function resolveProjectScope(cwd = process.cwd()) {
|
|
|
84
86
|
|
|
85
87
|
/**
|
|
86
88
|
* For project-scoped writers: returns the `<project>/.cx/<basename>` path
|
|
87
|
-
* when cwd is inside a Construct project, otherwise the
|
|
88
|
-
*
|
|
89
|
-
* resolves the directory side. ensureDir=true creates the parent dir.
|
|
89
|
+
* when cwd is inside a Construct project, otherwise the global
|
|
90
|
+
* `<doctorRoot>/<basename>` path. Callers pass just the file basename; this
|
|
91
|
+
* helper resolves the directory side. ensureDir=true creates the parent dir.
|
|
90
92
|
*/
|
|
91
93
|
export function resolveProjectScopedPath(basename, { cwd, ensureDir = true } = {}) {
|
|
92
94
|
const scope = resolveProjectScope(cwd ?? process.cwd());
|
|
93
|
-
const dir = scope ? scope.cxDir :
|
|
95
|
+
const dir = scope ? scope.cxDir : doctorRoot();
|
|
94
96
|
if (ensureDir && !fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
95
97
|
return path.join(dir, basename);
|
|
96
98
|
}
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
20
20
|
import { join } from 'node:path';
|
|
21
|
-
import { homedir } from 'node:os';
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
import { doctorRoot } from './config/xdg.mjs';
|
|
23
|
+
|
|
24
|
+
const CAPABILITY_CACHE_PATH = join(doctorRoot(), 'provider-capabilities.json');
|
|
24
25
|
const CAPABILITY_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24h
|
|
25
26
|
|
|
26
27
|
const ADAPTERS = {
|
|
@@ -110,7 +111,7 @@ function readCapabilityCache() {
|
|
|
110
111
|
|
|
111
112
|
function writeCapabilityCache(capabilities) {
|
|
112
113
|
try {
|
|
113
|
-
mkdirSync(
|
|
114
|
+
mkdirSync(doctorRoot(), { recursive: true });
|
|
114
115
|
writeFileSync(CAPABILITY_CACHE_PATH, JSON.stringify({
|
|
115
116
|
fetchedAt: Date.now(),
|
|
116
117
|
capabilities,
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
import fs from 'node:fs';
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { homedir } from 'node:os';
|
|
14
|
+
import { configDir } from '../config/xdg.mjs';
|
|
14
15
|
|
|
15
16
|
function authDir() {
|
|
16
17
|
const home = process.env.HOME || homedir();
|
|
17
|
-
const dir = path.join(home, '
|
|
18
|
+
const dir = path.join(configDir(home), 'auth');
|
|
18
19
|
try { fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); } catch { /* ignore */ }
|
|
19
20
|
return dir;
|
|
20
21
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* its token is rejected by the exchange endpoint.
|
|
12
12
|
*
|
|
13
13
|
* Credentials persist to two places: Construct's own store
|
|
14
|
-
* (
|
|
14
|
+
* (github-copilot.json under the XDG config dir auth/) and the de-facto standard
|
|
15
15
|
* ~/.config/github-copilot/apps.json that other tools read and write, so a login
|
|
16
16
|
* here is reusable elsewhere and an existing login is reused here. The access
|
|
17
17
|
* token is refreshed from the refresh token when it expires; the session token
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import fs from 'node:fs';
|
|
23
23
|
import path from 'node:path';
|
|
24
24
|
import os from 'node:os';
|
|
25
|
+
import { configDir } from '../config/xdg.mjs';
|
|
25
26
|
|
|
26
27
|
const CLIENT_ID = 'Iv1.b507a08c87ecfe98';
|
|
27
28
|
const DEVICE_CODE_URL = 'https://github.com/login/device/code';
|
|
@@ -53,7 +54,7 @@ function homeDir() {
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
function constructStorePath() {
|
|
56
|
-
return path.join(homeDir(), '
|
|
57
|
+
return path.join(configDir(homeDir()), 'auth', 'github-copilot.json');
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
function appsStorePath() {
|
package/lib/providers/creds.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/providers/creds.mjs — credential management for provider integrations.
|
|
3
3
|
*
|
|
4
|
-
* Reads and writes provider credentials stored in
|
|
4
|
+
* Reads and writes provider credentials stored in the user config.env (XDG config dir).
|
|
5
5
|
* Each provider block is delimited by comment markers so the file can be
|
|
6
6
|
* parsed without a full ini/dotenv library and can also be sourced by a shell.
|
|
7
7
|
*
|
|
@@ -24,11 +24,12 @@
|
|
|
24
24
|
import fs from 'node:fs';
|
|
25
25
|
import os from 'node:os';
|
|
26
26
|
import path from 'node:path';
|
|
27
|
+
import { configDir } from '../config/xdg.mjs';
|
|
27
28
|
|
|
28
29
|
const CONFIG_FILE = 'config.env';
|
|
29
30
|
|
|
30
31
|
function constructDir() {
|
|
31
|
-
return
|
|
32
|
+
return configDir();
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export function credsFilePath() {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Resolution order (last-write wins):
|
|
5
5
|
*
|
|
6
6
|
* 1. Built-in factories under `lib/providers/<id>/index.mjs` (loaded lazily).
|
|
7
|
-
* 2. Operator overrides at
|
|
7
|
+
* 2. Operator overrides at `providers.json` in the XDG config dir:
|
|
8
8
|
* { "providers": [{ "id": "...", "package": "...", "options": {} }] }
|
|
9
9
|
* 3. Project overrides at `<projectRoot>/.cx/providers.json` (same shape).
|
|
10
10
|
*
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import { readFileSync, existsSync } from 'node:fs';
|
|
24
|
-
import { homedir } from 'node:os';
|
|
25
24
|
import { join, isAbsolute, resolve, dirname } from 'node:path';
|
|
26
25
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
27
26
|
import { assertProviderContract } from './contract.mjs';
|
|
28
27
|
import { getBreaker } from './circuit-breaker.mjs';
|
|
28
|
+
import { configDir } from '../config/xdg.mjs';
|
|
29
29
|
|
|
30
30
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
31
31
|
|
|
@@ -92,7 +92,7 @@ async function instantiate(mod, options) {
|
|
|
92
92
|
|
|
93
93
|
function gatherOverrides(rootDir) {
|
|
94
94
|
const out = [];
|
|
95
|
-
const userConfig = readJsonIfExists(join(
|
|
95
|
+
const userConfig = readJsonIfExists(join(configDir(), 'providers.json'));
|
|
96
96
|
if (userConfig?.providers) out.push(...userConfig.providers);
|
|
97
97
|
const projectConfig = readJsonIfExists(join(rootDir, '.cx', 'providers.json'));
|
|
98
98
|
if (projectConfig?.providers) out.push(...projectConfig.providers);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Resolves a canonical environment variable (for example ANTHROPIC_API_KEY) by
|
|
5
5
|
* walking the same sources the rest of Construct trusts, in priority order:
|
|
6
|
-
* process env,
|
|
6
|
+
* process env, the XDG config dir config.env, ~/.env, the project .env, then shell rc
|
|
7
7
|
* files (.zshrc/.bashrc/.bash_profile/.profile). When the value carries a
|
|
8
8
|
* 1Password reference — a bare `op://vault/item/field` or the shell form
|
|
9
9
|
* `$(op read 'op://...')` — it is resolved through the `op` CLI and cached for
|
|
@@ -22,6 +22,7 @@ import path from 'node:path';
|
|
|
22
22
|
import os from 'node:os';
|
|
23
23
|
import { spawnSync } from 'node:child_process';
|
|
24
24
|
import { discoverAlternateRawForVar } from './credential-sources.mjs';
|
|
25
|
+
import { configDir } from '../config/xdg.mjs';
|
|
25
26
|
|
|
26
27
|
// The 1Password desktop integration prompts for biometric unlock on the first
|
|
27
28
|
// read after the vault locks, and that interactive round-trip routinely takes
|
|
@@ -111,7 +112,7 @@ function readShellRcVar(varName, home) {
|
|
|
111
112
|
function rawCandidate(varName, { env, cwd, home }) {
|
|
112
113
|
const direct = env?.[varName];
|
|
113
114
|
if (typeof direct === 'string' && direct.length > 0) return direct;
|
|
114
|
-
const files = [path.join(home, '
|
|
115
|
+
const files = [path.join(configDir(home), 'config.env'), path.join(home, '.env'), path.join(cwd, '.env')];
|
|
115
116
|
for (const file of files) {
|
|
116
117
|
const value = readDotenvVar(file, varName);
|
|
117
118
|
if (value) return value;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { homedir } from 'node:os';
|
|
12
|
+
import { doctorRoot } from './config/xdg.mjs';
|
|
12
13
|
|
|
13
14
|
const SESSION_IDLE_RESET_MS = 2 * 60 * 60 * 1000;
|
|
14
15
|
const REPEATED_READ_WARNING_THRESHOLD = 5;
|
|
@@ -20,7 +21,7 @@ const EFFICIENCY_LARGE_READ_LIMIT = 400;
|
|
|
20
21
|
|
|
21
22
|
export function readTrackerPaths(env = process.env) {
|
|
22
23
|
const home = env.CX_HOME_OVERRIDE || env.HOME || homedir();
|
|
23
|
-
const cxDir =
|
|
24
|
+
const cxDir = doctorRoot(home);
|
|
24
25
|
return {
|
|
25
26
|
cxDir,
|
|
26
27
|
efficiencyStore: join(cxDir, 'session-efficiency.json'),
|
|
@@ -21,7 +21,7 @@ import fs from 'node:fs';
|
|
|
21
21
|
import path from 'node:path';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
23
|
|
|
24
|
-
import { homeDir } from '../paths.mjs';
|
|
24
|
+
import { homeDir, constructDir } from '../paths.mjs';
|
|
25
25
|
import {
|
|
26
26
|
getCodexConfigPath,
|
|
27
27
|
buildCodexMcpEntry,
|
|
@@ -34,7 +34,7 @@ function escapeRegExp(value) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function registryMcpServers() {
|
|
37
|
-
const installed = path.join(
|
|
37
|
+
const installed = path.join(constructDir(), 'specialists', 'registry.json');
|
|
38
38
|
const bundled = fileURLToPath(new URL('../../specialists/registry.json', import.meta.url));
|
|
39
39
|
for (const src of [installed, bundled]) {
|
|
40
40
|
try {
|
package/lib/reflect.mjs
CHANGED
|
@@ -13,9 +13,10 @@ import { loadConstructEnv } from './env-config.mjs';
|
|
|
13
13
|
import { KNOWLEDGE_ROOT, KNOWLEDGE_SUBDIRS, inferKnowledgeTarget } from './knowledge/layout.mjs';
|
|
14
14
|
import { extractSessionObservation } from './reflect/extractor.mjs';
|
|
15
15
|
import { readContextState, contextSummaryLine } from './context-state.mjs';
|
|
16
|
+
import { configDir } from './config/xdg.mjs';
|
|
16
17
|
|
|
17
18
|
const HOME = process.env.HOME || process.env.USERPROFILE;
|
|
18
|
-
const USER_ENV_PATH = join(HOME, '
|
|
19
|
+
const USER_ENV_PATH = join(configDir(HOME), 'config.env');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Main reflect command handler.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/registry/generate-docs.mjs — generate reference docs from registry/capabilities.json.
|
|
3
3
|
*
|
|
4
|
-
* Writes docs/reference/capabilities.md so the human-readable capability matrix
|
|
4
|
+
* Writes docs/guides/reference/capabilities.md so the human-readable capability matrix
|
|
5
5
|
* cannot drift from the machine-readable registry.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -62,7 +62,7 @@ export function generateCapabilitiesDoc({ rootDir = REPO_ROOT, write = true } =
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const content = `${lines.join('\n')}\n`;
|
|
65
|
-
const out = path.join(rootDir, 'docs', 'reference', 'capabilities.md');
|
|
65
|
+
const out = path.join(rootDir, 'docs', 'guides', 'reference', 'capabilities.md');
|
|
66
66
|
if (!write) return { out, content, drift: fs.existsSync(out) ? fs.readFileSync(out, 'utf8') !== content : true };
|
|
67
67
|
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
68
68
|
fs.writeFileSync(out, content);
|