@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
package/lib/cost.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { readFileSync, existsSync, writeFileSync } from 'node:fs';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
|
+
import { doctorRoot } from './config/xdg.mjs';
|
|
11
12
|
|
|
12
13
|
function numberFrom(...values) {
|
|
13
14
|
for (const value of values) {
|
|
@@ -82,7 +83,7 @@ export function normalizeCostEntry(entry = {}) {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
export function readCostLog(homeDir) {
|
|
85
|
-
const logPath = join(homeDir, '
|
|
86
|
+
const logPath = join(doctorRoot(homeDir), 'session-cost.jsonl');
|
|
86
87
|
if (!existsSync(logPath)) return [];
|
|
87
88
|
try {
|
|
88
89
|
return readFileSync(logPath, 'utf8')
|
|
@@ -97,7 +98,7 @@ export function readCostLog(homeDir) {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
export function clearCostLog(homeDir) {
|
|
100
|
-
const logPath = join(homeDir, '
|
|
101
|
+
const logPath = join(doctorRoot(homeDir), 'session-cost.jsonl');
|
|
101
102
|
if (existsSync(logPath)) writeFileSync(logPath, '');
|
|
102
103
|
}
|
|
103
104
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* contracts as the single source of truth.
|
|
8
8
|
*
|
|
9
9
|
* Three decision sources are indexed:
|
|
10
|
-
* - ADRs docs/adr/*.md (status + supersedes parsed from body)
|
|
10
|
+
* - ADRs docs/decisions/adr/*.md (status + supersedes parsed from body)
|
|
11
11
|
* - Contracts specialists/contracts.json (executable postconditions = enforcement)
|
|
12
12
|
* - Rules rules/**\/*.md (enforcement link via frontmatter; bead wvbf.4)
|
|
13
13
|
*
|
|
@@ -37,7 +37,7 @@ const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
|
37
37
|
const ENFORCES_LINE_RE = /^(?:\/\/|#|\*)?\s*@enforces\s+(\S+)\s*$/;
|
|
38
38
|
|
|
39
39
|
// The body carries Status and Supersedes as markdown definition lines, not
|
|
40
|
-
// frontmatter (see docs/adr/templates/_template.md); parse them positionally.
|
|
40
|
+
// frontmatter (see docs/decisions/adr/templates/_template.md); parse them positionally.
|
|
41
41
|
|
|
42
42
|
// ADR field styles vary across the lane: inline bold (`- **Status**: x`,
|
|
43
43
|
// `**Status:** X`) and heading-then-value (`## Status` followed by the value on
|
|
@@ -121,7 +121,7 @@ function collectEnforcementBindings({ repoRoot }) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
function adrDecisions({ repoRoot, bindings }) {
|
|
124
|
-
const dir = join(repoRoot, 'docs', 'adr');
|
|
124
|
+
const dir = join(repoRoot, 'docs', 'decisions', 'adr');
|
|
125
125
|
const out = [];
|
|
126
126
|
if (!existsSync(dir)) return out;
|
|
127
127
|
for (const name of readdirSync(dir)) {
|
package/lib/distill.mjs
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
import { readdirSync, statSync, readFileSync, writeFileSync, existsSync, mkdirSync, openSync, readSync, closeSync } from 'fs';
|
|
10
10
|
import { join, extname, relative, dirname, basename } from 'path';
|
|
11
11
|
import { execSync, spawnSync } from 'child_process';
|
|
12
|
-
import { homedir } from 'os';
|
|
13
12
|
import { getActiveOverlays } from './headhunt.mjs';
|
|
13
|
+
import { doctorRoot } from './config/xdg.mjs';
|
|
14
14
|
import { withGenAiSpan, GenAiAttrs } from './telemetry/otel-tracer.mjs';
|
|
15
15
|
import {
|
|
16
16
|
EXTRACTABLE_DOCUMENT_EXTS,
|
|
@@ -347,7 +347,7 @@ export async function distill(dir, { format = 'summary', query = '', mode = 'aut
|
|
|
347
347
|
|
|
348
348
|
const prompt = structured.prompt;
|
|
349
349
|
if (mode === 'prompt' || !hasClaude()) {
|
|
350
|
-
const tmpFile = out || join(
|
|
350
|
+
const tmpFile = out || join(doctorRoot(), 'distill-prompt.txt');
|
|
351
351
|
try { mkdirSync(dirname(tmpFile), { recursive: true }); } catch { /* exists */ }
|
|
352
352
|
writeFileSync(tmpFile, prompt);
|
|
353
353
|
process.stderr.write(`\n⚠ Prompt written to: ${tmpFile}\n`);
|
package/lib/docs-verify.mjs
CHANGED
|
@@ -210,7 +210,9 @@ function gitTrackedMdFiles(cwd, dir) {
|
|
|
210
210
|
|
|
211
211
|
function checkIntakeTraceability(cwd) {
|
|
212
212
|
const intakeFedDirs = [
|
|
213
|
+
path.join(cwd, 'docs', 'specs', 'prd'),
|
|
213
214
|
path.join(cwd, 'docs', 'prd'),
|
|
215
|
+
path.join(cwd, 'docs', 'notes', 'research'),
|
|
214
216
|
path.join(cwd, 'docs', 'research'),
|
|
215
217
|
path.join(cwd, '.cx', 'knowledge', 'internal'),
|
|
216
218
|
];
|
package/lib/doctor/audit.mjs
CHANGED
|
@@ -6,17 +6,18 @@
|
|
|
6
6
|
* what the L0 layer did while they weren't looking — and the basis for rolling
|
|
7
7
|
* back a misfire if rules are wrong.
|
|
8
8
|
*
|
|
9
|
-
* Path:
|
|
9
|
+
* Path: <doctorRoot>/doctor-log.jsonl (overridable via CONSTRUCT_DOCTOR_ROOT for tests).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { existsSync, readFileSync, appendFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
13
|
-
import { homedir } from 'node:os';
|
|
14
13
|
import { join } from 'node:path';
|
|
15
14
|
|
|
15
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
16
|
+
|
|
16
17
|
const MAX_LINES = 2000;
|
|
17
18
|
|
|
18
19
|
function rootDir() {
|
|
19
|
-
return
|
|
20
|
+
return doctorRoot();
|
|
20
21
|
}
|
|
21
22
|
function logPath() {
|
|
22
23
|
return join(rootDir(), 'doctor-log.jsonl');
|
package/lib/doctor/cli.mjs
CHANGED
package/lib/doctor/index.mjs
CHANGED
|
@@ -7,12 +7,11 @@
|
|
|
7
7
|
* never starves the others. Termination via SIGTERM/SIGINT triggers a clean
|
|
8
8
|
* shutdown that records a final audit entry.
|
|
9
9
|
*
|
|
10
|
-
* State file:
|
|
10
|
+
* State file: doctor.json in the XDG state dir (pid, started, lastTick).
|
|
11
11
|
* Audit log: ~/.cx/doctor-log.jsonl.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { writeFileSync, existsSync, readFileSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
15
|
-
import { homedir } from 'node:os';
|
|
16
15
|
import { join } from 'node:path';
|
|
17
16
|
|
|
18
17
|
import { record } from './audit.mjs';
|
|
@@ -26,8 +25,9 @@ import * as consistency from './watchers/consistency.mjs';
|
|
|
26
25
|
import * as mcpProtocol from './watchers/mcp-protocol.mjs';
|
|
27
26
|
import * as cxBudget from './watchers/cx-budget.mjs';
|
|
28
27
|
import * as graphStaleness from './watchers/graph-staleness.mjs';
|
|
28
|
+
import { stateDir } from '../config/xdg.mjs';
|
|
29
29
|
|
|
30
|
-
const STATE_PATH = join(
|
|
30
|
+
const STATE_PATH = join(stateDir(), 'doctor.json');
|
|
31
31
|
const WATCHERS = [disk, cost, processPressure, serviceHealth, bdWatch, handoffs, consistency, mcpProtocol, cxBudget, graphStaleness];
|
|
32
32
|
|
|
33
33
|
let running = false;
|
|
@@ -35,7 +35,7 @@ let timers = [];
|
|
|
35
35
|
let lastTickAt = {};
|
|
36
36
|
|
|
37
37
|
function writeState(state) {
|
|
38
|
-
const dir =
|
|
38
|
+
const dir = stateDir();
|
|
39
39
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
40
40
|
writeFileSync(STATE_PATH, JSON.stringify({ ...state, updatedAt: Date.now() }));
|
|
41
41
|
}
|
package/lib/doctor/report.mjs
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { existsSync, readFileSync } from 'node:fs';
|
|
11
|
-
import { homedir } from 'node:os';
|
|
12
11
|
import { join } from 'node:path';
|
|
13
12
|
|
|
14
13
|
import { recent } from './audit.mjs';
|
|
14
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
15
15
|
import { getTotalDailySpend, getDailySpend, totalBudget, personaBudget, dayKey } from '../cost-ledger.mjs';
|
|
16
16
|
|
|
17
17
|
function parseWindow(arg) {
|
|
@@ -67,8 +67,8 @@ export async function runReport(args = []) {
|
|
|
67
67
|
const since = Date.now() - windowMs;
|
|
68
68
|
|
|
69
69
|
const auditEntries = recent({ since, limit: 10000 });
|
|
70
|
-
const events = readJsonl(join(
|
|
71
|
-
const pending = readJsonl(join(
|
|
70
|
+
const events = readJsonl(join(doctorRoot(), 'events.jsonl')).filter((e) => e.ts >= since);
|
|
71
|
+
const pending = readJsonl(join(doctorRoot(), 'role-pending.jsonl')).filter((e) => e.ts >= since);
|
|
72
72
|
|
|
73
73
|
const summary = summarizeActions(auditEntries);
|
|
74
74
|
const eventsByType = groupBy(events, (e) => e.type);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/doctor/source-checkout.mjs — distinguish Construct source tree vs npm install.
|
|
3
|
+
*
|
|
4
|
+
* `isConstructPackageRepo` is true for both checkouts and consumer installs.
|
|
5
|
+
* Source-only doctor checks (dashboard static export, certification role cards)
|
|
6
|
+
* should run only when this returns true.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
|
|
12
|
+
export function isConstructSourceCheckout(rootDir) {
|
|
13
|
+
const root = path.resolve(rootDir);
|
|
14
|
+
return fs.existsSync(path.join(root, 'apps', 'dashboard'))
|
|
15
|
+
|| fs.existsSync(path.join(root, 'tests', 'certification', 'specialists'));
|
|
16
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Every 5 min, polls bd for open issues labeled `next:cx-<role>` where the
|
|
5
5
|
* target persona is onboarded in the role framework. Newly-seen issues are
|
|
6
6
|
* enqueued as pending invocations so session-start surfaces them to
|
|
7
|
-
* Construct. Seen issues are remembered in
|
|
7
|
+
* Construct. Seen issues are remembered in <doctorRoot>/bd-watch-seen.json to
|
|
8
8
|
* prevent re-emission while the label remains.
|
|
9
9
|
*
|
|
10
10
|
* Closes a gap in agent-tracker: agent-tracker only catches handoffs that
|
|
@@ -16,20 +16,20 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
|
16
16
|
import { spawnSync } from 'node:child_process';
|
|
17
17
|
import { appendBounded } from '../../logging/rotate.mjs';
|
|
18
18
|
import { resolveProjectScope } from '../../project-root.mjs';
|
|
19
|
-
import { homedir } from 'node:os';
|
|
20
19
|
import { join } from 'node:path';
|
|
21
20
|
|
|
22
21
|
import { record } from '../audit.mjs';
|
|
23
22
|
import { listOnboardedPersonas, loadManifest } from '../../roles/manifest.mjs';
|
|
23
|
+
import { doctorRoot } from '../../config/xdg.mjs';
|
|
24
24
|
|
|
25
25
|
export const name = 'bd-watch';
|
|
26
26
|
export const intervalMs = 5 * 60 * 1000;
|
|
27
27
|
|
|
28
|
-
const SEEN_PATH = join(
|
|
29
|
-
const PENDING_PATH = join(
|
|
28
|
+
const SEEN_PATH = join(doctorRoot(), 'bd-watch-seen.json');
|
|
29
|
+
const PENDING_PATH = join(doctorRoot(), 'role-pending.jsonl');
|
|
30
30
|
|
|
31
31
|
function ensureDir() {
|
|
32
|
-
const dir =
|
|
32
|
+
const dir = doctorRoot();
|
|
33
33
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
13
|
-
import { homedir } from 'node:os';
|
|
14
13
|
import { join } from 'node:path';
|
|
15
14
|
|
|
16
15
|
import { record } from '../audit.mjs';
|
|
16
|
+
import { doctorRoot } from '../../config/xdg.mjs';
|
|
17
17
|
import {
|
|
18
18
|
getTotalDailySpend,
|
|
19
19
|
getDailySpend,
|
|
@@ -26,15 +26,15 @@ import {
|
|
|
26
26
|
export const name = 'cost';
|
|
27
27
|
export const intervalMs = 10 * 60 * 1000;
|
|
28
28
|
|
|
29
|
-
const SESSION_COST_PATH = join(
|
|
30
|
-
const STATE_PATH = join(
|
|
29
|
+
const SESSION_COST_PATH = join(doctorRoot(), 'session-cost.jsonl');
|
|
30
|
+
const STATE_PATH = join(doctorRoot(), 'cost-watcher-state.json');
|
|
31
31
|
|
|
32
32
|
function loadState() {
|
|
33
33
|
if (!existsSync(STATE_PATH)) return { lastIngestedTsMs: 0 };
|
|
34
34
|
try { return JSON.parse(readFileSync(STATE_PATH, 'utf8')); } catch { return { lastIngestedTsMs: 0 }; }
|
|
35
35
|
}
|
|
36
36
|
function saveState(state) {
|
|
37
|
-
const dir =
|
|
37
|
+
const dir = doctorRoot();
|
|
38
38
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
39
39
|
writeFileSync(STATE_PATH, JSON.stringify(state));
|
|
40
40
|
}
|
|
@@ -16,6 +16,7 @@ import { join } from 'node:path';
|
|
|
16
16
|
|
|
17
17
|
import { record } from '../audit.mjs';
|
|
18
18
|
import { escalate } from '../escalate.mjs';
|
|
19
|
+
import { cacheDir } from '../../config/xdg.mjs';
|
|
19
20
|
|
|
20
21
|
export const name = 'disk';
|
|
21
22
|
export const intervalMs = 5 * 60 * 1000;
|
|
@@ -54,7 +55,7 @@ function rotateTarget({ path: relPath, maxLines }) {
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
function pruneRuntimeLogs() {
|
|
57
|
-
const runtimeDir = join(
|
|
58
|
+
const runtimeDir = join(cacheDir(), '.runtime');
|
|
58
59
|
if (!existsSync(runtimeDir)) return { pruned: 0 };
|
|
59
60
|
const maxAgeMs = 7 * 24 * 60 * 60 * 1000;
|
|
60
61
|
const now = Date.now();
|
|
@@ -99,11 +100,11 @@ export async function tick() {
|
|
|
99
100
|
kind: 'action',
|
|
100
101
|
watcher: name,
|
|
101
102
|
action: 'prune',
|
|
102
|
-
target: '.construct/.runtime/',
|
|
103
|
+
target: '.cache/construct/.runtime/',
|
|
103
104
|
summary: `pruned ${runtimePrune.pruned} stale runtime log(s) (>7 days)`,
|
|
104
105
|
context: runtimePrune,
|
|
105
106
|
});
|
|
106
|
-
actions.push({ type: 'prune', target: '.construct/.runtime/' });
|
|
107
|
+
actions.push({ type: 'prune', target: '.cache/construct/.runtime/' });
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
const free = freeBytesAt(homedir());
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
|
|
14
14
|
import { spawnSync, spawn } from 'node:child_process';
|
|
15
15
|
import { existsSync, readFileSync, openSync, mkdirSync } from 'node:fs';
|
|
16
|
-
import { homedir } from 'node:os';
|
|
17
16
|
import { dirname, join } from 'node:path';
|
|
18
17
|
import { fileURLToPath } from 'node:url';
|
|
19
18
|
|
|
20
19
|
import { record } from '../audit.mjs';
|
|
21
20
|
import { escalate } from '../escalate.mjs';
|
|
21
|
+
import { configDir, stateDir, cacheDir } from '../../config/xdg.mjs';
|
|
22
22
|
|
|
23
23
|
export const name = 'service-health';
|
|
24
24
|
export const intervalMs = 60 * 1000;
|
|
@@ -56,7 +56,7 @@ function probeTcp(host, port, timeoutMs = 1500) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function probeDashboardPort() {
|
|
59
|
-
const statePath = join(
|
|
59
|
+
const statePath = join(stateDir(), 'dashboard.json');
|
|
60
60
|
if (!existsSync(statePath)) return true;
|
|
61
61
|
try {
|
|
62
62
|
const state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
@@ -68,7 +68,7 @@ function probeDashboardPort() {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function probeMemoryHealth() {
|
|
71
|
-
const envPath = join(
|
|
71
|
+
const envPath = join(configDir(), 'config.env');
|
|
72
72
|
if (!existsSync(envPath)) return true;
|
|
73
73
|
try {
|
|
74
74
|
const env = readFileSync(envPath, 'utf8');
|
|
@@ -87,7 +87,7 @@ function dockerRestart(container) {
|
|
|
87
87
|
|
|
88
88
|
function detachSpawn(command, args, logName) {
|
|
89
89
|
try {
|
|
90
|
-
const runtimeDir = join(
|
|
90
|
+
const runtimeDir = join(cacheDir(), '.runtime');
|
|
91
91
|
if (!existsSync(runtimeDir)) mkdirSync(runtimeDir, { recursive: true });
|
|
92
92
|
const logPath = join(runtimeDir, logName);
|
|
93
93
|
const fd = openSync(logPath, 'a');
|
|
@@ -106,7 +106,7 @@ function respawnViaConstructUp() {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
function respawnCm() {
|
|
109
|
-
const envPath = join(
|
|
109
|
+
const envPath = join(configDir(), 'config.env');
|
|
110
110
|
let port = '4231';
|
|
111
111
|
try {
|
|
112
112
|
if (existsSync(envPath)) {
|
package/lib/document-export.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Export **source** is Construct-authored markdown (typed artifacts). **Output**
|
|
7
7
|
* spans many distributable formats. Ingest (separate pipeline) accepts PDF, Office,
|
|
8
|
-
* email, AV, and plain text — see docs/reference/document-io.md.
|
|
8
|
+
* email, AV, and plain text — see docs/guides/reference/document-io.md.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import fs from 'node:fs';
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
import { exportDeckPptx, pptxgenPresent } from './deck-export-pptx.mjs';
|
|
28
28
|
import { pdfUsesBundledBrandSans } from './brand-fonts.mjs';
|
|
29
29
|
import { convertDocxToDoc, libreOfficeInstallHint, libreOfficePresent, resolveLibreOfficeBin } from './libreoffice-export.mjs';
|
|
30
|
+
import { resolveExportBranding } from './export-branding.mjs';
|
|
30
31
|
|
|
31
32
|
export const EXPORT_FORMATS = ['pdf', 'docx', 'doc', 'deck', 'pptx', 'html', 'rtf', 'odt', 'epub', 'tex', 'txt', 'md', 'mdx'];
|
|
32
33
|
|
|
@@ -153,6 +154,7 @@ function installHint(name) {
|
|
|
153
154
|
if (name === 'mmdc') return 'Install mermaid-cli for mermaid blocks (`npm install -g @mermaid-js/mermaid-cli`).';
|
|
154
155
|
if (name === 'pptxgenjs') return 'Install pptxgenjs to enable PPTX export (`npm install pptxgenjs` in the Construct package).';
|
|
155
156
|
if (name === 'construct-deck.html') return 'Bundled deck template missing from templates/distribution/construct-deck.html (Construct install integrity issue).';
|
|
157
|
+
if (name === 'construct-reference.docx') return 'Bundled branded reference doc missing from templates/distribution/construct-reference.docx; construct-branded DOCX export degrades to pandoc default styling. Regenerate from a source checkout with `node scripts/build-reference-docx.mjs`.';
|
|
156
158
|
if (name === 'libreoffice') return libreOfficeInstallHint();
|
|
157
159
|
return `Install ${name} to enable this export format.`;
|
|
158
160
|
}
|
|
@@ -160,13 +162,17 @@ function installHint(name) {
|
|
|
160
162
|
export function detect(format, env = process.env, {
|
|
161
163
|
figures = false,
|
|
162
164
|
artifactType = null,
|
|
165
|
+
branding = 'construct',
|
|
163
166
|
cwd = process.cwd(),
|
|
164
167
|
repoRoot = REPO_ROOT,
|
|
165
168
|
} = {}) {
|
|
166
|
-
const
|
|
167
|
-
|
|
169
|
+
const brandingPolicy = resolveExportBranding(format, branding);
|
|
170
|
+
const config = format === 'pptx' && brandingPolicy.applied === 'plain'
|
|
171
|
+
? { engine: 'pandoc', writer: 'pptx', pdfEngine: null, extraBinaries: [] }
|
|
172
|
+
: FORMAT_ENGINES[format];
|
|
173
|
+
if (!config) return { ok: false, format, branding: brandingPolicy, present: false, missing: [], message: `Unsupported format: ${format}. Supported: ${EXPORT_FORMATS.join(', ')}.` };
|
|
168
174
|
if (config.engine === 'copy') {
|
|
169
|
-
return { ok: true, format, figures: false, present: true, binaries: [], missing: [], message: `Ready: source emit (${format})` };
|
|
175
|
+
return { ok: true, format, branding: brandingPolicy, figures: false, present: true, binaries: [], missing: [], message: `Ready: source emit (${format})` };
|
|
170
176
|
}
|
|
171
177
|
const required = [];
|
|
172
178
|
if (config.engine === 'pptxgenjs') {
|
|
@@ -174,13 +180,19 @@ export function detect(format, env = process.env, {
|
|
|
174
180
|
} else {
|
|
175
181
|
required.push(config.engine, ...(config.extraBinaries || []));
|
|
176
182
|
}
|
|
177
|
-
if (format === 'pdf') {
|
|
183
|
+
if (format === 'pdf' && brandingPolicy.applied === 'construct') {
|
|
178
184
|
const template = detectPdfTemplate({ artifactType, cwd, repoRoot });
|
|
179
185
|
if (!template.present) required.push('construct-pdf.typ');
|
|
180
186
|
}
|
|
181
|
-
if (format === 'deck' && !deckTemplatePath(repoRoot)) {
|
|
187
|
+
if (format === 'deck' && brandingPolicy.applied === 'construct' && !deckTemplatePath(repoRoot)) {
|
|
182
188
|
required.push('construct-deck.html');
|
|
183
189
|
}
|
|
190
|
+
if (format === 'html' && brandingPolicy.applied === 'construct' && !htmlTemplatePath(repoRoot)) required.push('construct-web.html');
|
|
191
|
+
|
|
192
|
+
// The DOCX reference doc only restyles pandoc output; pandoc emits a valid file without it and
|
|
193
|
+
// the export passes --reference-doc only when present, so unlike the PDF/HTML/deck templates a
|
|
194
|
+
// missing reference degrades gracefully rather than blocking the export.
|
|
195
|
+
|
|
184
196
|
if (format === 'doc' && !libreOfficePresent(env)) {
|
|
185
197
|
required.push('libreoffice');
|
|
186
198
|
}
|
|
@@ -193,9 +205,10 @@ export function detect(format, env = process.env, {
|
|
|
193
205
|
if (name.endsWith('.lua')) {
|
|
194
206
|
return { name, path: fs.existsSync(diagramFilterPath()) ? diagramFilterPath() : null, version: null };
|
|
195
207
|
}
|
|
196
|
-
if (name
|
|
208
|
+
if (name === 'construct-deck.html') {
|
|
197
209
|
return { name, path: deckTemplatePath(repoRoot), version: null };
|
|
198
210
|
}
|
|
211
|
+
if (name === 'construct-web.html') return { name, path: htmlTemplatePath(repoRoot), version: null };
|
|
199
212
|
if (name === 'pptxgenjs') {
|
|
200
213
|
return { name, path: pptxgenPresent() ? 'bundled' : null, version: null };
|
|
201
214
|
}
|
|
@@ -213,6 +226,7 @@ export function detect(format, env = process.env, {
|
|
|
213
226
|
return {
|
|
214
227
|
ok: true,
|
|
215
228
|
format,
|
|
229
|
+
branding: brandingPolicy,
|
|
216
230
|
figures,
|
|
217
231
|
present: missing.length === 0,
|
|
218
232
|
binaries: status,
|
|
@@ -290,6 +304,7 @@ export function exportMarkdown({
|
|
|
290
304
|
outputPath,
|
|
291
305
|
format,
|
|
292
306
|
figures = false,
|
|
307
|
+
branding = 'construct',
|
|
293
308
|
artifactType = null,
|
|
294
309
|
env = process.env,
|
|
295
310
|
spawnFn = spawnSync,
|
|
@@ -304,21 +319,24 @@ export function exportMarkdown({
|
|
|
304
319
|
const resolvedType = artifactType || metadata.artifactType || null;
|
|
305
320
|
const effectiveEnv = figures ? buildDistributionDiagramEnv(env) : env;
|
|
306
321
|
|
|
307
|
-
const
|
|
322
|
+
const brandingPolicy = resolveExportBranding(format, branding);
|
|
323
|
+
const detection = detect(format, effectiveEnv, { figures, artifactType: resolvedType, branding, cwd, repoRoot });
|
|
308
324
|
if (!detection.present) {
|
|
309
325
|
return { ok: false, format, inputPath, missing: detection.missing, message: detection.message };
|
|
310
326
|
}
|
|
311
327
|
|
|
312
|
-
const config =
|
|
328
|
+
const config = format === 'pptx' && brandingPolicy.applied === 'plain'
|
|
329
|
+
? { engine: 'pandoc', writer: 'pptx', pdfEngine: null, extraBinaries: [] }
|
|
330
|
+
: FORMAT_ENGINES[format];
|
|
313
331
|
const target = path.resolve(outputPath || defaultOutputPath(inputPath, format));
|
|
314
332
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
315
333
|
|
|
316
334
|
if (config.engine === 'copy') {
|
|
317
|
-
return exportSourceCopy({ inputPath, target, format });
|
|
335
|
+
return { ...exportSourceCopy({ inputPath, target, format }), branding: brandingPolicy };
|
|
318
336
|
}
|
|
319
337
|
|
|
320
|
-
if (format === 'pptx') {
|
|
321
|
-
return exportDeckPptx({ inputPath, outputPath: target, metadata, repoRoot });
|
|
338
|
+
if (format === 'pptx' && brandingPolicy.applied === 'construct') {
|
|
339
|
+
return { ...exportDeckPptx({ inputPath, outputPath: target, metadata, repoRoot }), branding: brandingPolicy };
|
|
322
340
|
}
|
|
323
341
|
|
|
324
342
|
if (format === 'doc') {
|
|
@@ -328,6 +346,7 @@ export function exportMarkdown({
|
|
|
328
346
|
outputPath: intermediate,
|
|
329
347
|
format: 'docx',
|
|
330
348
|
figures,
|
|
349
|
+
branding,
|
|
331
350
|
artifactType: resolvedType,
|
|
332
351
|
env: effectiveEnv,
|
|
333
352
|
spawnFn,
|
|
@@ -356,12 +375,13 @@ export function exportMarkdown({
|
|
|
356
375
|
pdfEngine: null,
|
|
357
376
|
figures,
|
|
358
377
|
template: null,
|
|
378
|
+
branding: brandingPolicy,
|
|
359
379
|
message: loResult.message,
|
|
360
380
|
};
|
|
361
381
|
}
|
|
362
382
|
|
|
363
383
|
const prepared = prepareExportInput(inputPath, { format, figures, metadata });
|
|
364
|
-
const templatePath = format === 'pdf'
|
|
384
|
+
const templatePath = format === 'pdf' && brandingPolicy.applied === 'construct'
|
|
365
385
|
? resolvePdfTemplatePath({ artifactType: resolvedType, cwd, repoRoot })
|
|
366
386
|
: null;
|
|
367
387
|
|
|
@@ -378,25 +398,26 @@ export function exportMarkdown({
|
|
|
378
398
|
});
|
|
379
399
|
|
|
380
400
|
const args = ['-f', 'markdown', '-o', target, '--standalone'];
|
|
401
|
+
if (config.writer) args.push('-t', config.writer);
|
|
381
402
|
if (config.pdfEngine) args.push(`--pdf-engine=${config.pdfEngine}`);
|
|
382
403
|
if (format === 'pdf') {
|
|
383
404
|
if (templatePath) args.push('--template', templatePath);
|
|
384
|
-
args.push(...pdfEngineFontOpts(repoRoot));
|
|
405
|
+
if (brandingPolicy.applied === 'construct') args.push(...pdfEngineFontOpts(repoRoot));
|
|
385
406
|
args.push(...metadataArgs);
|
|
386
407
|
} else if (format === 'html') {
|
|
387
408
|
const htmlTpl = htmlTemplatePath(repoRoot);
|
|
388
|
-
if (htmlTpl) args.push('--template', htmlTpl);
|
|
409
|
+
if (htmlTpl && brandingPolicy.applied === 'construct') args.push('--template', htmlTpl);
|
|
389
410
|
args.push('--embed-resources');
|
|
390
411
|
args.push(...metadataArgs);
|
|
391
412
|
} else if (format === 'deck') {
|
|
392
413
|
const deckTpl = deckTemplatePath(repoRoot);
|
|
393
|
-
if (deckTpl) args.push('--template', deckTpl);
|
|
414
|
+
if (deckTpl && brandingPolicy.applied === 'construct') args.push('--template', deckTpl);
|
|
394
415
|
args.push('--section-divs');
|
|
395
416
|
args.push('--embed-resources');
|
|
396
417
|
args.push(...metadataArgs);
|
|
397
418
|
} else if (format === 'docx') {
|
|
398
419
|
const ref = docxReferencePath(repoRoot);
|
|
399
|
-
if (ref) args.push('--reference-doc', ref);
|
|
420
|
+
if (ref && brandingPolicy.applied === 'construct') args.push('--reference-doc', ref);
|
|
400
421
|
args.push(...metadataArgs);
|
|
401
422
|
}
|
|
402
423
|
if (figures && fs.existsSync(diagramFilterPath())) {
|
|
@@ -443,6 +464,7 @@ export function exportMarkdown({
|
|
|
443
464
|
pdfEngine: config.pdfEngine,
|
|
444
465
|
figures,
|
|
445
466
|
template: templatePath,
|
|
467
|
+
branding: brandingPolicy,
|
|
446
468
|
message: `Wrote ${path.relative(process.cwd(), target)}`,
|
|
447
469
|
};
|
|
448
470
|
}
|
package/lib/document-extract.mjs
CHANGED
|
@@ -345,8 +345,8 @@ function extractPdf(filePath) {
|
|
|
345
345
|
if (extracted.text.length < threshold) {
|
|
346
346
|
const ocrHint = process.env.CONSTRUCT_PDF_OCR_HINT || 'none';
|
|
347
347
|
const hintMsg = ocrHint !== 'none'
|
|
348
|
-
? `OCR backend hint: ${ocrHint}. See docs/intake/scanned-pdfs.md.`
|
|
349
|
-
: 'Set CONSTRUCT_PDF_OCR_HINT=tesseract|pdfocr to enable OCR. See docs/intake/scanned-pdfs.md.';
|
|
348
|
+
? `OCR backend hint: ${ocrHint}. See docs/guides/intake/scanned-pdfs.md.`
|
|
349
|
+
: 'Set CONSTRUCT_PDF_OCR_HINT=tesseract|pdfocr to enable OCR. See docs/guides/intake/scanned-pdfs.md.';
|
|
350
350
|
extracted.droppedInfo.push(makeDropInfo({
|
|
351
351
|
kind: 'scanned-pdf',
|
|
352
352
|
count: pages,
|
|
@@ -692,7 +692,7 @@ export function extractDocumentText(filePath, { maxChars = null } = {}) {
|
|
|
692
692
|
// Throw a typed error so callers can route to .cx/intake/needs-asr/.
|
|
693
693
|
if (AUDIO_VIDEO_EXTS.has(extension)) {
|
|
694
694
|
const err = new Error(
|
|
695
|
-
`Audio/video extraction requires ASR. Set CONSTRUCT_ASR_BACKEND=whisper|assemblyai to enable. See docs/intake/audio-video.md.`,
|
|
695
|
+
`Audio/video extraction requires ASR. Set CONSTRUCT_ASR_BACKEND=whisper|assemblyai to enable. See docs/guides/intake/audio-video.md.`,
|
|
696
696
|
);
|
|
697
697
|
err.code = 'ASR_REQUIRED';
|
|
698
698
|
err.extension = extension;
|
package/lib/document-ingest.mjs
CHANGED
|
@@ -137,19 +137,29 @@ function collectInputFiles(inputPath, { maxDepth = 10 } = {}) {
|
|
|
137
137
|
return files.sort((a, b) => a.localeCompare(b));
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// Ingest writes a markdown rendering of the source, preserving the source name
|
|
141
|
+
// for provenance (report.pdf → report.pdf.md). A source already in markdown keeps
|
|
142
|
+
// its name unchanged so it never gains a second extension (_template.md →
|
|
143
|
+
// _template.md, not _template.md.md).
|
|
144
|
+
|
|
145
|
+
function markdownOutputName(sourcePath) {
|
|
146
|
+
const base = basename(sourcePath);
|
|
147
|
+
return /\.md$/i.test(base) ? base : `${base}.md`;
|
|
148
|
+
}
|
|
149
|
+
|
|
140
150
|
function resolveOutputPath(sourcePath, { cwd, outputPath, outputDir, target }) {
|
|
141
151
|
if (outputPath) return normalizeOutputPath(outputPath, cwd);
|
|
142
152
|
if (target === 'sibling') {
|
|
143
|
-
return join(dirname(sourcePath),
|
|
153
|
+
return join(dirname(sourcePath), markdownOutputName(sourcePath));
|
|
144
154
|
}
|
|
145
155
|
// knowledge/<subdir> targets land in .cx/knowledge/<subdir>/
|
|
146
156
|
if (target.startsWith('knowledge/')) {
|
|
147
157
|
const sub = target.slice('knowledge/'.length);
|
|
148
158
|
const resolvedDir = normalizeOutputPath(`${KNOWLEDGE_ROOT}/${sub}`, cwd);
|
|
149
|
-
return join(resolvedDir,
|
|
159
|
+
return join(resolvedDir, markdownOutputName(sourcePath));
|
|
150
160
|
}
|
|
151
161
|
const resolvedDir = normalizeOutputPath(outputDir || DEFAULT_TARGET_DIR, cwd);
|
|
152
|
-
return join(resolvedDir,
|
|
162
|
+
return join(resolvedDir, markdownOutputName(sourcePath));
|
|
153
163
|
}
|
|
154
164
|
|
|
155
165
|
// The high-fidelity (docling) path provisions a Python venv and downloads ML
|
package/lib/efficiency.mjs
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, readFileSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
|
+
import { doctorRoot } from './config/xdg.mjs';
|
|
9
10
|
|
|
10
11
|
const HIGH_BYTES_THRESHOLD = 750_000;
|
|
11
12
|
const HIGH_REPEATED_RATIO = 0.4;
|
|
12
13
|
const HIGH_LARGE_READ_COUNT = 3;
|
|
13
14
|
|
|
14
15
|
export function readEfficiencyLog(homeDir) {
|
|
15
|
-
const filePath = join(homeDir, '
|
|
16
|
+
const filePath = join(doctorRoot(homeDir), 'session-efficiency.json');
|
|
16
17
|
if (!existsSync(filePath)) return null;
|
|
17
18
|
try {
|
|
18
19
|
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
import fs from 'node:fs';
|
|
12
12
|
import path from 'node:path';
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
14
15
|
|
|
15
16
|
export class ApprovalQueue {
|
|
16
17
|
#items = new Map(); // id → ApprovalItem
|
|
@@ -171,6 +172,6 @@ export class ApprovalQueue {
|
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
static defaultPersistPath() {
|
|
174
|
-
return path.join(
|
|
175
|
+
return path.join(doctorRoot(), 'approval-queue.jsonl');
|
|
175
176
|
}
|
|
176
177
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/auto-sources.mjs — resolve embed auto-discovery sources from project config.
|
|
3
|
+
*
|
|
4
|
+
* When embed.yaml is absent, builds provider source records from typed
|
|
5
|
+
* construct.config.json targets merged with legacy env lists. Falls back
|
|
6
|
+
* to broad provider defaults only when no targets are configured.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { loadProjectConfig } from '../config/project-config.mjs';
|
|
10
|
+
import {
|
|
11
|
+
resolveEffectiveSourceTargetsFromConfig,
|
|
12
|
+
targetsToEmbedSources,
|
|
13
|
+
legacyEnvSourceTargets,
|
|
14
|
+
} from '../config/source-targets.mjs';
|
|
15
|
+
|
|
16
|
+
export function resolveAutoEmbedSources({ cwd = process.cwd(), env = process.env, registry }) {
|
|
17
|
+
const { config } = loadProjectConfig(cwd, env);
|
|
18
|
+
const targets = resolveEffectiveSourceTargetsFromConfig(config, env);
|
|
19
|
+
|
|
20
|
+
if (targets.length === 0) {
|
|
21
|
+
return registry.autoSources(env);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const configuredProviders = new Set(targets.map((t) => t.provider));
|
|
25
|
+
const hasLegacyForUnconfigured = (provider) =>
|
|
26
|
+
legacyEnvSourceTargets(env).some((t) => t.provider === provider);
|
|
27
|
+
|
|
28
|
+
const sources = targetsToEmbedSources(targets);
|
|
29
|
+
|
|
30
|
+
for (const name of registry.names()) {
|
|
31
|
+
if (configuredProviders.has(name)) continue;
|
|
32
|
+
if (hasLegacyForUnconfigured(name)) continue;
|
|
33
|
+
const instance = registry.get(name);
|
|
34
|
+
if (typeof instance?.defaultSources !== 'function') continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
return sources.filter((s) => {
|
|
39
|
+
const key = `${s.provider}:${(s.refs ?? []).join(',')}:${JSON.stringify(s.repos ?? s.project ?? s.team ?? s.channels ?? '')}`;
|
|
40
|
+
if (seen.has(key)) return false;
|
|
41
|
+
seen.add(key);
|
|
42
|
+
return true;
|
|
43
|
+
});
|
|
44
|
+
}
|