@geraldmaron/construct 1.2.1 → 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 +6 -6
- package/bin/construct +41 -67
- 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 +3 -3
- 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/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-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 +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/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
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
|
|
26
26
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
27
27
|
import { join, dirname } from 'node:path';
|
|
28
|
-
import { homedir } from 'node:os';
|
|
29
28
|
import { createHash } from 'node:crypto';
|
|
30
29
|
import { appendBounded, readLastLineAcrossSegments } from '../logging/rotate.mjs';
|
|
31
30
|
import { resolveProjectScopedPath } from '../project-root.mjs';
|
|
31
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
32
32
|
|
|
33
|
-
const CX_DIR =
|
|
33
|
+
const CX_DIR = doctorRoot();
|
|
34
34
|
const LAST_AGENT = join(CX_DIR, 'last-agent.json');
|
|
35
35
|
|
|
36
36
|
// contract-violations are PROJECT-SCOPED: resolves to
|
package/lib/cost-ledger.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* before they blow the daily budget. Separate from lib/roles/ because both
|
|
6
6
|
* the gateway and the cost watcher consume it.
|
|
7
7
|
*
|
|
8
|
-
* Storage:
|
|
8
|
+
* Storage: <doctorRoot>/cost-ledger.json — small JSON object keyed by `<dayKey>:<personaId>`.
|
|
9
9
|
* Rotates: entries older than 30 days are pruned on each write.
|
|
10
10
|
*
|
|
11
11
|
* Budgets are env-driven defaults; explicit `CONSTRUCT_BUDGET_<PERSONA>` wins.
|
|
@@ -15,16 +15,17 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
18
|
-
import { homedir } from 'node:os';
|
|
19
18
|
import { join } from 'node:path';
|
|
20
19
|
import { cwd as procCwd } from 'node:process';
|
|
21
20
|
|
|
21
|
+
import { doctorRoot } from './config/xdg.mjs';
|
|
22
|
+
|
|
22
23
|
const PRUNE_DAYS = 30;
|
|
23
24
|
const DEFAULT_PER_PERSONA = 10.0;
|
|
24
25
|
const DEFAULT_TOTAL = 50.0;
|
|
25
26
|
|
|
26
27
|
function rootDir() {
|
|
27
|
-
return
|
|
28
|
+
return doctorRoot();
|
|
28
29
|
}
|
|
29
30
|
function ledgerPath() {
|
|
30
31
|
return join(rootDir(), 'cost-ledger.json');
|
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);
|
|
@@ -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';
|
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
|
}
|
package/lib/embed/cli.mjs
CHANGED
|
@@ -21,6 +21,8 @@ import fs from 'node:fs';
|
|
|
21
21
|
import os from 'node:os';
|
|
22
22
|
import path from 'node:path';
|
|
23
23
|
import { spawn } from 'node:child_process';
|
|
24
|
+
import { configDir } from '../config/xdg.mjs';
|
|
25
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
24
26
|
|
|
25
27
|
const STATE_FILE = 'embed-daemon.json';
|
|
26
28
|
const LOG_FILE = 'embed-daemon.log';
|
|
@@ -30,7 +32,7 @@ const LOG_FILE = 'embed-daemon.log';
|
|
|
30
32
|
// ---------------------------------------------------------------------------
|
|
31
33
|
|
|
32
34
|
function runtimeDir(homeDir = os.homedir()) {
|
|
33
|
-
return path.join(homeDir, '
|
|
35
|
+
return path.join(doctorRoot(homeDir), 'runtime');
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
function statePath(homeDir = os.homedir()) {
|
|
@@ -273,8 +275,8 @@ async function cmdEmbedStart(args, { homeDir = os.homedir(), rootDir, _workerPat
|
|
|
273
275
|
const { config } = parseArgs(args);
|
|
274
276
|
const configPath = config
|
|
275
277
|
? path.resolve(config)
|
|
276
|
-
: fs.existsSync(path.join(
|
|
277
|
-
? path.join(
|
|
278
|
+
: fs.existsSync(path.join(configDir(), 'embed.yaml'))
|
|
279
|
+
? path.join(configDir(), 'embed.yaml')
|
|
278
280
|
: null;
|
|
279
281
|
|
|
280
282
|
const workerPath = _workerPath ?? path.join(rootDir, 'lib', 'embed', 'worker.mjs');
|
|
@@ -374,7 +376,7 @@ async function cmdEmbedStatus(_args, { homeDir = os.homedir() } = {}) {
|
|
|
374
376
|
process.stdout.write(` pid: ${state.pid}\n`);
|
|
375
377
|
process.stdout.write(` config: ${state.configPath}\n`);
|
|
376
378
|
process.stdout.write(` rootDir: ${rootDir}\n`);
|
|
377
|
-
process.stdout.write(` inbox: ${path.join(rootDir, '
|
|
379
|
+
process.stdout.write(` inbox: ${path.join(rootDir, 'inbox')}\n`);
|
|
378
380
|
process.stdout.write(` started at: ${state.startedAt}\n`);
|
|
379
381
|
process.stdout.write(` log: ${logPath(homeDir)}\n`);
|
|
380
382
|
}
|
|
@@ -387,7 +389,7 @@ async function cmdEmbedSnapshot(args, { homeDir = os.homedir() } = {}) {
|
|
|
387
389
|
const { config } = parseArgs(args);
|
|
388
390
|
const configPath = config
|
|
389
391
|
? path.resolve(config)
|
|
390
|
-
: path.join(
|
|
392
|
+
: path.join(configDir(), 'embed.yaml');
|
|
391
393
|
|
|
392
394
|
const { loadEmbedConfig, EMPTY_CONFIG } = await import('./config.mjs');
|
|
393
395
|
const { SnapshotEngine, renderMarkdown } = await import('./snapshot.mjs');
|
package/lib/embed/config.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/embed/config.mjs — embed mode configuration schema and loader.
|
|
3
3
|
*
|
|
4
|
-
* Reads embed.yaml from
|
|
4
|
+
* Reads embed.yaml from the XDG config dir (or a supplied path) and validates
|
|
5
5
|
* the file against the expected schema. Returns a parsed, normalized config object.
|
|
6
6
|
*
|
|
7
7
|
* Schema:
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
import fs from 'node:fs';
|
|
56
56
|
import os from 'node:os';
|
|
57
57
|
import path from 'node:path';
|
|
58
|
+
import { configDir, stateDir } from '../config/xdg.mjs';
|
|
58
59
|
|
|
59
60
|
const DEFAULTS = {
|
|
60
61
|
snapshot: { intervalMs: 300_000, maxItems: 100 },
|
|
@@ -74,13 +75,13 @@ const DEFAULTS = {
|
|
|
74
75
|
* ref: github.com/org/project-y
|
|
75
76
|
* path: /Users/user/Github/project-y # both — prefer local, fall back to remote
|
|
76
77
|
* - type: workspace # always present as fallback
|
|
77
|
-
* path:
|
|
78
|
+
* path: <XDG_STATE_HOME>/construct/workspace
|
|
78
79
|
*
|
|
79
80
|
* roles:
|
|
80
81
|
* primary: product-manager # shapes embed analysis lens
|
|
81
82
|
* secondary: architect # persistent background concern
|
|
82
83
|
*/
|
|
83
|
-
export const DEFAULT_WORKSPACE_PATH = path.join(
|
|
84
|
+
export const DEFAULT_WORKSPACE_PATH = path.join(stateDir(), 'workspace');
|
|
84
85
|
|
|
85
86
|
export const WORKSPACE_DOCS_LANES = ['adrs', 'prds', 'memos', 'notes', 'intake'];
|
|
86
87
|
|
|
@@ -99,7 +100,7 @@ export const DEFAULT_OPERATING_PROFILE = {
|
|
|
99
100
|
},
|
|
100
101
|
focalResources: [
|
|
101
102
|
{ type: 'plan', path: 'plan.md', purpose: 'active implementation plan' },
|
|
102
|
-
{ type: 'architecture', path: 'docs/concepts/architecture.md', purpose: 'system architecture and operating model' },
|
|
103
|
+
{ type: 'architecture', path: 'docs/guides/concepts/architecture.md', purpose: 'system architecture and operating model' },
|
|
103
104
|
{ type: 'knowledge', path: '.cx/knowledge', purpose: 'typed project knowledge and ingested evidence' },
|
|
104
105
|
{ type: 'runtime', path: '.cx/roadmap.md', purpose: 'generated roadmap output' },
|
|
105
106
|
],
|
|
@@ -351,5 +352,5 @@ export function parseEmbedYaml(text) {
|
|
|
351
352
|
}
|
|
352
353
|
|
|
353
354
|
export function defaultEmbedConfigPath() {
|
|
354
|
-
return path.join(
|
|
355
|
+
return path.join(configDir(), 'embed.yaml');
|
|
355
356
|
}
|
package/lib/embed/daemon.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - approval-expiry — expires stale approval queue items
|
|
14
14
|
* - eval-dataset-sync — syncs scored traces to telemetry dataset items
|
|
15
15
|
* - prompt-regression-check — detects low-quality score clusters per promptHash
|
|
16
|
-
* - inbox-watcher — ingests new files from
|
|
16
|
+
* - inbox-watcher — ingests new files from inbox/ + CX_INBOX_DIRS into
|
|
17
17
|
* observations (agnostic: specs, ADRs, meeting notes, etc.)
|
|
18
18
|
* - roadmap — cross-references open items + observations → .cx/roadmap.md
|
|
19
19
|
*
|
|
@@ -48,6 +48,7 @@ import { emitEmbedNotification, notifySlack } from './notifications.mjs';
|
|
|
48
48
|
import { intentToCategory } from './providers/slack.mjs';
|
|
49
49
|
import { runTelemetrySetup } from '../telemetry/setup.mjs';
|
|
50
50
|
import { emitTraceEvent, newTraceId, newSpanId } from '../worker/trace.mjs';
|
|
51
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
54
|
* Log a non-fatal error to stderr. Use instead of empty catch {} blocks
|
|
@@ -121,8 +122,8 @@ function resolveDaemonStatePath(env = process.env) {
|
|
|
121
122
|
|
|
122
123
|
// ─── Self-repair helpers ────────────────────────────────────────────────────
|
|
123
124
|
|
|
124
|
-
const LOCK_PATH = join(
|
|
125
|
-
const DAEMON_STATE_PATH = join(
|
|
125
|
+
const LOCK_PATH = join(doctorRoot(), 'sync.lock');
|
|
126
|
+
const DAEMON_STATE_PATH = join(doctorRoot(), 'runtime', 'embed-daemon.json');
|
|
126
127
|
|
|
127
128
|
/**
|
|
128
129
|
* Run in-process health checks and fix what can be fixed.
|
|
@@ -562,7 +563,7 @@ export class EmbedDaemon {
|
|
|
562
563
|
'daemon-log-rotation',
|
|
563
564
|
60_000,
|
|
564
565
|
async () => {
|
|
565
|
-
const logPath = join(
|
|
566
|
+
const logPath = join(doctorRoot(), 'runtime', 'embed-daemon.log');
|
|
566
567
|
const capMb = Number(process.env.CONSTRUCT_EMBED_LOG_MAX_MB) || 50;
|
|
567
568
|
const maxBytes = Math.floor(capMb * 1024 * 1024);
|
|
568
569
|
const maxSegments = Number(process.env.CONSTRUCT_EMBED_LOG_MAX_SEGMENTS) || 5;
|
|
@@ -729,7 +730,7 @@ this.#scheduler.register(
|
|
|
729
730
|
// Every 2 min — scan configured inbox dirs for new files, ingest them, and
|
|
730
731
|
// record observations. Agnostic to content: specs, ADRs, meeting notes,
|
|
731
732
|
// internal docs, PDFs, Office files — anything on the local filesystem.
|
|
732
|
-
// Dirs: <rootDir
|
|
733
|
+
// Dirs: <rootDir>/inbox/ always; CX_INBOX_DIRS env for extra paths.
|
|
733
734
|
this.#scheduler.register(
|
|
734
735
|
'inbox-watcher',
|
|
735
736
|
2 * 60_000,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Registers a node:fs.watch listener on each inbox directory and triggers
|
|
5
5
|
* `InboxWatcher.poll()` shortly after filesystem activity settles, so files
|
|
6
|
-
* dropped into
|
|
6
|
+
* dropped into `inbox/` flow through ingest + the review queue in
|
|
7
7
|
* seconds rather than waiting for the scheduler's next interval.
|
|
8
8
|
*
|
|
9
9
|
* Two limits keep this stable across platforms:
|