@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
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
|
|
18
18
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
19
19
|
import { execSync } from 'node:child_process';
|
|
20
|
-
import { homedir } from 'node:os';
|
|
21
20
|
import { join } from 'node:path';
|
|
22
21
|
import { emitRoleEvent } from '../roles/hook-emit.mjs';
|
|
22
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
23
23
|
|
|
24
|
-
const STATE_PATH = join(
|
|
24
|
+
const STATE_PATH = join(doctorRoot(), 'readme-age-state.json');
|
|
25
25
|
const STALE_DAYS = 90;
|
|
26
26
|
const SUPPRESS_DAYS = 7;
|
|
27
27
|
|
|
@@ -14,18 +14,19 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
16
16
|
import { join, dirname } from 'path';
|
|
17
|
-
import { homedir } from 'os';
|
|
18
17
|
import { spawn } from 'child_process';
|
|
19
18
|
import { fileURLToPath } from 'url';
|
|
20
19
|
|
|
20
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
21
|
+
|
|
21
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
23
|
const CONSTRUCT_BIN = join(__dirname, '..', '..', 'bin', 'construct');
|
|
23
24
|
|
|
24
25
|
const OPTIMIZATION_THRESHOLD = 0.7;
|
|
25
26
|
const MIN_TRACES_PER_AGENT = 3;
|
|
26
27
|
const OPTIMIZATION_DAYS = 7;
|
|
27
|
-
const OPTIMIZATION_LOG_DIR = join(
|
|
28
|
-
const REVIEWS_DIR = join(
|
|
28
|
+
const OPTIMIZATION_LOG_DIR = join(doctorRoot(), 'optimization-logs');
|
|
29
|
+
const REVIEWS_DIR = join(doctorRoot(), 'performance-reviews');
|
|
29
30
|
|
|
30
31
|
mkdirSync(OPTIMIZATION_LOG_DIR, { recursive: true });
|
|
31
32
|
|
|
@@ -24,6 +24,7 @@ import { listObservations, searchObservations } from '../observation-store.mjs';
|
|
|
24
24
|
import { countEntities } from '../entity-store.mjs';
|
|
25
25
|
import { logHookFailure } from './_lib/log.mjs';
|
|
26
26
|
import { resolveHookOutputMode, writeHookContext } from './_lib/output-mode.mjs';
|
|
27
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
27
28
|
|
|
28
29
|
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
29
30
|
const CONSTRUCT_BIN = resolve(MODULE_DIR, '..', '..', 'bin', 'construct');
|
|
@@ -64,11 +65,11 @@ function fileIsFresh(path) {
|
|
|
64
65
|
// stale context files inject only the digest line as fallback (Tier 2 → Tier 1
|
|
65
66
|
// degradation), full body only when modified within the freshness window.
|
|
66
67
|
const localCtx = join(cwd, '.cx', 'context.md');
|
|
67
|
-
const globalCtx = join(
|
|
68
|
+
const globalCtx = join(doctorRoot(), '.cx', 'context.md');
|
|
68
69
|
let context = '';
|
|
69
70
|
let contextStale = false;
|
|
70
71
|
const localState = readContextState(cwd);
|
|
71
|
-
const globalState = localState ? null : readContextState(
|
|
72
|
+
const globalState = localState ? null : readContextState(doctorRoot());
|
|
72
73
|
const stateContext = localState || globalState;
|
|
73
74
|
const stateContextPath = localState ? localCtx : globalCtx;
|
|
74
75
|
if (stateContext?.markdown) {
|
|
@@ -151,7 +152,7 @@ try {
|
|
|
151
152
|
|
|
152
153
|
// Record memory injection stats for the Stop hook to persist into .cx/memory-stats.jsonl.
|
|
153
154
|
try {
|
|
154
|
-
const memStatsPath = join(
|
|
155
|
+
const memStatsPath = join(doctorRoot(), 'session-memory-stats.json');
|
|
155
156
|
const injected = observationsNote ? (observationsNote.match(/^- /gm) || []).length : 0;
|
|
156
157
|
writeFileSync(memStatsPath, JSON.stringify({
|
|
157
158
|
project: projectName,
|
|
@@ -162,7 +163,7 @@ try {
|
|
|
162
163
|
} catch { /* best effort */ }
|
|
163
164
|
|
|
164
165
|
// Pending typecheck warning
|
|
165
|
-
const tcPath = join(
|
|
166
|
+
const tcPath = join(doctorRoot(), 'pending-typecheck.txt');
|
|
166
167
|
let pendingNote = '';
|
|
167
168
|
try {
|
|
168
169
|
const pending = existsSync(tcPath) ? readFileSync(tcPath, 'utf8').split('\n').filter(Boolean) : [];
|
|
@@ -265,28 +266,31 @@ const selfKnowledgeNote = '\nSelf-knowledge: call `knowledge_search` for any Con
|
|
|
265
266
|
let sourcesNote = '';
|
|
266
267
|
let embedStatusNote = '';
|
|
267
268
|
try {
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
const
|
|
269
|
+
const { loadProjectConfig } = await import('../config/project-config.mjs');
|
|
270
|
+
const { resolveEffectiveSourceTargetsFromConfig } = await import('../config/source-targets.mjs');
|
|
271
|
+
const { config } = loadProjectConfig(process.cwd(), process.env);
|
|
272
|
+
const targets = resolveEffectiveSourceTargetsFromConfig(config, process.env);
|
|
273
|
+
const repos = targets.filter((t) => t.provider === 'github').map((t) => t.selector.repo);
|
|
274
|
+
const jiraTargets = targets.filter((t) => t.provider === 'jira');
|
|
275
|
+
const linearTargets = targets.filter((t) => t.provider === 'linear');
|
|
276
|
+
const slackTargets = targets.filter((t) => t.provider === 'slack');
|
|
277
|
+
const hasSources = targets.length > 0;
|
|
272
278
|
if (hasSources) {
|
|
273
279
|
const parts = [];
|
|
274
280
|
if (repos.length > 0) parts.push(`GitHub repos: ${repos.join(', ')}`);
|
|
275
|
-
if (
|
|
276
|
-
if (
|
|
277
|
-
|
|
278
|
-
// tool for questions about them. The "fetch first, don't answer from
|
|
279
|
-
// memory" policy lives in the persona.
|
|
281
|
+
if (jiraTargets.length > 0) parts.push(`Jira projects: ${jiraTargets.map((t) => t.selector.project).join(', ')}`);
|
|
282
|
+
if (linearTargets.length > 0) parts.push(`Linear teams: ${linearTargets.map((t) => t.selector.team).join(', ')}`);
|
|
283
|
+
if (slackTargets.length > 0) parts.push(`Slack channels: ${slackTargets.map((t) => t.selector.channel).join(', ')}`);
|
|
280
284
|
sourcesNote = '\nProvider sources wired: ' + parts.join(' · ') +
|
|
281
285
|
'. Use `provider_fetch` for any question about them.\n';
|
|
282
286
|
}
|
|
287
|
+
} catch { /* best effort */ }
|
|
283
288
|
|
|
284
|
-
|
|
289
|
+
try {
|
|
285
290
|
const { resolveEmbedStatus, autoStartEmbedIfNeeded } = await import('../embed/cli.mjs');
|
|
286
291
|
const embedStatus = resolveEmbedStatus(process.env);
|
|
287
292
|
if (embedStatus.level !== 'none') {
|
|
288
293
|
embedStatusNote = `\n## Embed daemon\n${embedStatus.label} · ${embedStatus.detail}\n`;
|
|
289
|
-
// Auto-start if CX_AUTO_EMBED=1 and daemon is stopped
|
|
290
294
|
if (embedStatus.level === 'stopped' && process.env.CX_AUTO_EMBED === '1') {
|
|
291
295
|
const result = await autoStartEmbedIfNeeded(process.env);
|
|
292
296
|
if (result.started) {
|
|
@@ -397,7 +401,7 @@ try {
|
|
|
397
401
|
try {
|
|
398
402
|
const sessionId = input?.session_id || input?.sessionId;
|
|
399
403
|
if (sessionId) {
|
|
400
|
-
const bootstrapPath = join(
|
|
404
|
+
const bootstrapPath = join(doctorRoot(), "bootstrap-state.json");
|
|
401
405
|
let state = {};
|
|
402
406
|
try { state = JSON.parse(readFileSync(bootstrapPath, "utf8")) || {}; } catch {}
|
|
403
407
|
const now = Date.now();
|
|
@@ -27,6 +27,7 @@ import { captureSessionArtifacts } from '../artifact-capture.mjs';
|
|
|
27
27
|
import { appendSessionStats } from '../memory-stats.mjs';
|
|
28
28
|
import { estimateUsageCost } from '../telemetry/model-pricing-catalog.mjs';
|
|
29
29
|
import { flushReadTrackerDeltas } from '../read-tracker-store.mjs';
|
|
30
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
30
31
|
|
|
31
32
|
function loadTranscriptCheckpoints(checkpointPath) {
|
|
32
33
|
try {
|
|
@@ -148,12 +149,12 @@ if (raw) process.stdout.write(raw);
|
|
|
148
149
|
try { flushReadTrackerDeltas({ env: process.env }); } catch { /* non-critical */ }
|
|
149
150
|
|
|
150
151
|
const home = homedir();
|
|
151
|
-
const tsResultPath = join(home, '
|
|
152
|
-
const warnFlagsPath = join(home, '
|
|
153
|
-
const countPath = join(home, '
|
|
154
|
-
const costLogPath = join(home, '
|
|
155
|
-
const lastAgentPath = join(home, '
|
|
156
|
-
const checkpointPath = join(home, '
|
|
152
|
+
const tsResultPath = join(doctorRoot(home), 'ts-result.txt');
|
|
153
|
+
const warnFlagsPath = join(doctorRoot(home), 'warn-flags.txt');
|
|
154
|
+
const countPath = join(doctorRoot(home), 'files-changed-count.txt');
|
|
155
|
+
const costLogPath = join(doctorRoot(home), 'session-cost.jsonl');
|
|
156
|
+
const lastAgentPath = join(doctorRoot(home), 'last-agent.json');
|
|
157
|
+
const checkpointPath = join(doctorRoot(home), 'transcript-checkpoints.json');
|
|
157
158
|
|
|
158
159
|
// Consume-clear like warn-flags/files-changed below: a TS result is reported
|
|
159
160
|
// once, not re-fired on every subsequent no-op Stop. stop-typecheck rewrites
|
|
@@ -304,7 +305,7 @@ try {
|
|
|
304
305
|
}
|
|
305
306
|
} catch { /* non-critical */ }
|
|
306
307
|
|
|
307
|
-
const memStatsPath = join(home, '
|
|
308
|
+
const memStatsPath = join(doctorRoot(home), 'session-memory-stats.json');
|
|
308
309
|
try {
|
|
309
310
|
if (existsSync(memStatsPath)) {
|
|
310
311
|
const memStats = JSON.parse(readFileSync(memStatsPath, 'utf8'));
|
|
@@ -323,7 +324,7 @@ try {
|
|
|
323
324
|
// repeat Stop events within the window to avoid review-spam.
|
|
324
325
|
try {
|
|
325
326
|
const homeDir = process.env.HOME || homedir();
|
|
326
|
-
const stampPath = join(homeDir, '
|
|
327
|
+
const stampPath = join(doctorRoot(homeDir), 'performance-reviews', '.last-run');
|
|
327
328
|
let lastRun = 0;
|
|
328
329
|
try { lastRun = Number(readFileSync(stampPath, 'utf8')) || 0; } catch { /* fresh */ }
|
|
329
330
|
if (Date.now() - lastRun > 24 * 60 * 60 * 1000) {
|
|
@@ -15,6 +15,7 @@ import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
|
15
15
|
import { execSync } from 'child_process';
|
|
16
16
|
import { homedir } from 'os';
|
|
17
17
|
import { join, dirname } from 'path';
|
|
18
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
18
19
|
|
|
19
20
|
// Stop hooks must echo stdin to stdout
|
|
20
21
|
let raw = '';
|
|
@@ -22,9 +23,9 @@ try { raw = readFileSync(0, 'utf8'); } catch { /* no stdin */ }
|
|
|
22
23
|
if (raw) process.stdout.write(raw);
|
|
23
24
|
|
|
24
25
|
const home = homedir();
|
|
25
|
-
const tcPath = join(home, '
|
|
26
|
-
const tsResultPath = join(home, '
|
|
27
|
-
const warnFlagsPath = join(home, '
|
|
26
|
+
const tcPath = join(doctorRoot(home), 'pending-typecheck.txt');
|
|
27
|
+
const tsResultPath = join(doctorRoot(home), 'ts-result.txt');
|
|
28
|
+
const warnFlagsPath = join(doctorRoot(home), 'warn-flags.txt');
|
|
28
29
|
|
|
29
30
|
// Read pending files
|
|
30
31
|
const pending = existsSync(tcPath)
|
package/lib/hooks/test-watch.mjs
CHANGED
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { readFileSync, existsSync, writeFileSync } from 'node:fs';
|
|
18
|
-
import { homedir } from 'node:os';
|
|
19
18
|
import { join } from 'node:path';
|
|
20
19
|
import { logHookFailure } from './_lib/log.mjs';
|
|
21
20
|
import { emitRoleEvent } from '../roles/hook-emit.mjs';
|
|
21
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
22
22
|
|
|
23
23
|
let input = {};
|
|
24
24
|
try { input = JSON.parse(readFileSync(0, 'utf8')); }
|
|
@@ -44,7 +44,7 @@ emitRoleEvent({
|
|
|
44
44
|
context: { command, exitCode, failedNames },
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
const flakeTrackerPath = join(
|
|
47
|
+
const flakeTrackerPath = join(doctorRoot(), 'test-watch-state.json');
|
|
48
48
|
try {
|
|
49
49
|
const state = existsSync(flakeTrackerPath) ? JSON.parse(readFileSync(flakeTrackerPath, 'utf8')) : {};
|
|
50
50
|
const now = Date.now();
|
package/lib/host-disposition.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* lib/host-disposition.mjs — single source of truth for the disposition of
|
|
3
3
|
* every artifact Construct creates in a host project (ADR-0027 §1).
|
|
4
4
|
*
|
|
5
|
-
* Scope model (see docs/concepts/project-scopes.md):
|
|
5
|
+
* Scope model (see docs/guides/concepts/project-scopes.md):
|
|
6
6
|
* templates/, docs/ — package/repo durable sources (committed)
|
|
7
7
|
* .construct/ — per-project launcher (ignored)
|
|
8
8
|
* .cx/ — per-project runtime: intake, oracle, observations, demos (ignored)
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
import { readFileSync, existsSync } from 'node:fs';
|
|
25
25
|
import { extname, join } from 'node:path';
|
|
26
26
|
import { homedir } from 'node:os';
|
|
27
|
+
import { configDir } from '../config/xdg.mjs';
|
|
27
28
|
|
|
28
29
|
import {
|
|
29
30
|
AUDIO_VIDEO_EXTS,
|
|
@@ -75,7 +76,7 @@ function isAnthropic(provider, model) {
|
|
|
75
76
|
function resolveKey(varName, env, allowAmbient) {
|
|
76
77
|
if (env[varName] && typeof env[varName] === 'string' && env[varName].length > 0) return env[varName];
|
|
77
78
|
if (!allowAmbient) return null;
|
|
78
|
-
for (const file of [join(
|
|
79
|
+
for (const file of [join(configDir(), 'config.env'), join(homedir(), '.env')]) {
|
|
79
80
|
try {
|
|
80
81
|
if (!existsSync(file)) continue;
|
|
81
82
|
const m = readFileSync(file, 'utf8').match(new RegExp(`^${varName}=["']?(.+?)["']?$`, 'm'));
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Heuristics intentionally err on the side of "skip" — a directory is treated
|
|
14
14
|
* as a real lane only when it carries at least one markdown file. An empty
|
|
15
|
-
* docs/meetings/ that an earlier init scaffolded does NOT register; that lets
|
|
15
|
+
* docs/notes/meetings/ that an earlier init scaffolded does NOT register; that lets
|
|
16
16
|
* re-running init be a no-op rather than a contradictory "skip what we just
|
|
17
17
|
* created" message.
|
|
18
18
|
*
|
|
19
19
|
* Issue #97 motivates this: `construct init` on an existing project with
|
|
20
20
|
* `internal/meetings/`, a custom `ingest` script, and root-level `templates/`
|
|
21
|
-
* created `docs/meetings/`,
|
|
21
|
+
* created `docs/notes/meetings/`, `inbox/`, and per-lane `templates/` folders
|
|
22
22
|
* that conflicted with the existing workflow.
|
|
23
23
|
*/
|
|
24
24
|
|
package/lib/init/doc-lanes.mjs
CHANGED
|
@@ -32,12 +32,6 @@ export const DOC_LANES = {
|
|
|
32
32
|
description: 'User-facing release notes and version history entries.',
|
|
33
33
|
templates: ['changelog-entry.md'],
|
|
34
34
|
},
|
|
35
|
-
intake: {
|
|
36
|
-
title: 'Intake',
|
|
37
|
-
dir: 'intake',
|
|
38
|
-
description: 'Intake batch records that explain what arrived, why it matters, and how it should be ingested.',
|
|
39
|
-
templates: ['__intake-template__'],
|
|
40
|
-
},
|
|
41
35
|
memos: {
|
|
42
36
|
title: 'Memos',
|
|
43
37
|
dir: 'memos',
|
|
@@ -88,11 +82,11 @@ export const DOC_LANES = {
|
|
|
88
82
|
},
|
|
89
83
|
};
|
|
90
84
|
|
|
91
|
-
export const LANE_ORDER = ['adrs', 'briefs', 'changelogs', '
|
|
85
|
+
export const LANE_ORDER = ['adrs', 'briefs', 'changelogs', 'memos', 'meetings', 'notes', 'onboarding', 'postmortems', 'prds', 'rfcs', 'runbooks'];
|
|
92
86
|
|
|
93
87
|
export const DOC_PRESETS = {
|
|
94
|
-
lean: ['adrs', '
|
|
95
|
-
product: ['adrs', '
|
|
88
|
+
lean: ['adrs', 'memos', 'meetings', 'notes', 'prds'],
|
|
89
|
+
product: ['adrs', 'memos', 'meetings', 'notes', 'prds', 'rfcs'],
|
|
96
90
|
full: LANE_ORDER,
|
|
97
91
|
};
|
|
98
92
|
|
|
@@ -107,7 +101,6 @@ export const LANE_ALIASES = {
|
|
|
107
101
|
changelogs: 'changelogs',
|
|
108
102
|
releases: 'changelogs',
|
|
109
103
|
release: 'changelogs',
|
|
110
|
-
intake: 'intake',
|
|
111
104
|
memo: 'memos',
|
|
112
105
|
memos: 'memos',
|
|
113
106
|
meeting: 'meetings',
|
package/lib/init-docs.mjs
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* lib/init-docs.mjs — stand up a documentation system for a project.
|
|
4
4
|
*
|
|
5
5
|
* Intentionally separate from `construct init`. Creates the docs surface only:
|
|
6
|
-
* docs/README.md plus selected lane directories such as docs/adr/, docs/intake/,
|
|
7
|
-
* docs/memos/, docs/notes/, docs/prds/, and docs/rfcs/ with starter templates
|
|
6
|
+
* docs/README.md plus selected lane directories such as docs/decisions/adr/, docs/intake/,
|
|
7
|
+
* docs/notes/memos/, docs/notes/, templates/docs/prds/, and templates/docs/rfcs/ with starter templates
|
|
8
8
|
* copied into per-lane templates/ directories from Construct's template library.
|
|
9
9
|
*
|
|
10
10
|
* Usage:
|
|
@@ -290,15 +290,6 @@ function buildLaneReadme(laneKey) {
|
|
|
290
290
|
const filename = index === 0 ? "_template.md" : templateName.replace(/\.md$/, ".template.md");
|
|
291
291
|
return `- [${filename}](./templates/${filename})`;
|
|
292
292
|
});
|
|
293
|
-
const usageSection = laneKey === "intake"
|
|
294
|
-
? `
|
|
295
|
-
## Intake flow
|
|
296
|
-
|
|
297
|
-
- Drop source files into either [\`.cx/inbox/\`](../../.cx/inbox/) or this lane's directory when you want Construct to ingest them.
|
|
298
|
-
- Run \`construct ingest ./docs/intake --sync\` or \`construct ingest ./.cx/inbox --sync\` to convert supported files into retrieval-ready markdown, or let the embed daemon watch those drop zones automatically.
|
|
299
|
-
- Durable ingested knowledge lands under \`.cx/knowledge/internal/\` by default, which is where Construct's learning and search paths already operate.
|
|
300
|
-
`
|
|
301
|
-
: "";
|
|
302
293
|
|
|
303
294
|
return `<!--
|
|
304
295
|
docs/${dirName}/README.md — lane guide for ${title}.
|
|
@@ -314,7 +305,6 @@ ${description}
|
|
|
314
305
|
## Starter templates
|
|
315
306
|
|
|
316
307
|
${templateLines.join("\n")}
|
|
317
|
-
${usageSection}
|
|
318
308
|
`;
|
|
319
309
|
}
|
|
320
310
|
|
|
@@ -432,39 +422,6 @@ Promote durable decisions into ADRs, memos, or PRDs when needed.
|
|
|
432
422
|
`;
|
|
433
423
|
}
|
|
434
424
|
|
|
435
|
-
function buildIntakeTemplate() {
|
|
436
|
-
return `<!--
|
|
437
|
-
docs/intake/templates/_template.md — starter template for logging an intake batch.
|
|
438
|
-
|
|
439
|
-
Use this when you want a durable record of what was dropped into intake and why.
|
|
440
|
-
Raw source files belong in .cx/inbox/ until they are ingested.
|
|
441
|
-
-->
|
|
442
|
-
|
|
443
|
-
# Intake Batch: {title}
|
|
444
|
-
|
|
445
|
-
- **Date**: {YYYY-MM-DD}
|
|
446
|
-
- **Owner**: {name}
|
|
447
|
-
- **Source**: {vendor, teammate, export, upload}
|
|
448
|
-
|
|
449
|
-
## What arrived
|
|
450
|
-
|
|
451
|
-
<!-- Files, folders, or links dropped into .cx/inbox/. -->
|
|
452
|
-
|
|
453
|
-
## Why it matters
|
|
454
|
-
|
|
455
|
-
<!-- Why Construct should ingest this material. -->
|
|
456
|
-
|
|
457
|
-
## Ingest plan
|
|
458
|
-
|
|
459
|
-
- Run: \`construct ingest ./.cx/inbox --sync\`
|
|
460
|
-
- Target: \`.cx/knowledge/internal/\` unless a different knowledge target is more appropriate
|
|
461
|
-
|
|
462
|
-
## Notes
|
|
463
|
-
|
|
464
|
-
<!-- Caveats, access concerns, or cleanup notes. -->
|
|
465
|
-
`;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
425
|
function titleCase(value) {
|
|
469
426
|
return value
|
|
470
427
|
.split(/[-_\s]+/)
|
|
@@ -532,7 +489,6 @@ function copyLaneTemplates(laneKey) {
|
|
|
532
489
|
const content =
|
|
533
490
|
templateName === "__notes-template__" ? buildNotesTemplate()
|
|
534
491
|
: templateName === '__meeting-notes-template__' ? buildMeetingNotesTemplate()
|
|
535
|
-
: templateName === "__intake-template__" ? buildIntakeTemplate()
|
|
536
492
|
: fs.readFileSync(path.join(TEMPLATE_DIR, templateName), "utf8");
|
|
537
493
|
writeIfMissing(path.join(laneRoot, "templates", outputName), content);
|
|
538
494
|
}
|
|
@@ -632,14 +588,16 @@ async function main() {
|
|
|
632
588
|
if (withArchitecture) {
|
|
633
589
|
writeIfMissing(path.join(docsDir, "architecture.md"), buildArchitectureDoc(projectName, allScaffoldedLaneKeys));
|
|
634
590
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
591
|
+
const inboxDecision = shouldSkipProjectInbox(detection, { force: forceScaffold });
|
|
592
|
+
if (inboxDecision.skip) {
|
|
593
|
+
process.stdout.write(`[init:docs] skipping inbox/ — ${inboxDecision.reason}. Run with --force to scaffold anyway.\n`);
|
|
594
|
+
skipped.push('inbox/ (deferred to existing intake)');
|
|
595
|
+
} else {
|
|
596
|
+
// Single canonical drop zone (ADR-0045 §C): the project-root inbox/, with
|
|
597
|
+
// a gitignored .staging/ for atomic rename-in handoff. Independent of any
|
|
598
|
+
// docs lane.
|
|
599
|
+
|
|
600
|
+
fs.mkdirSync(path.join(target, 'inbox', '.staging'), { recursive: true });
|
|
643
601
|
}
|
|
644
602
|
|
|
645
603
|
for (const { laneKey, reason } of deferredLanes) {
|
package/lib/init-unified.mjs
CHANGED
|
@@ -175,10 +175,7 @@ function detectProjectType(targetPath) {
|
|
|
175
175
|
function suggestLanes(targetPath, projectType) {
|
|
176
176
|
const suggestions = [];
|
|
177
177
|
const files = fs.readdirSync(targetPath);
|
|
178
|
-
|
|
179
|
-
// Always suggest intake for collecting feedback
|
|
180
|
-
suggestions.push({ lane: 'intake', reason: 'Collect incoming requests and feedback', recommended: true });
|
|
181
|
-
|
|
178
|
+
|
|
182
179
|
// ADRs for any non-trivial project
|
|
183
180
|
if (projectType.type !== 'docs') {
|
|
184
181
|
suggestions.push({ lane: 'adrs', reason: 'Track architectural decisions', recommended: true });
|
|
@@ -365,33 +362,6 @@ Attendees: {{attendees}}
|
|
|
365
362
|
- [ ] @owner: Task description (due: {{date}})`;
|
|
366
363
|
}
|
|
367
364
|
|
|
368
|
-
function buildIntakeTemplate() {
|
|
369
|
-
return `# Intake: {{source}}
|
|
370
|
-
|
|
371
|
-
Received: {{date}}
|
|
372
|
-
From: {{from}}
|
|
373
|
-
Priority: {{priority}}
|
|
374
|
-
|
|
375
|
-
## What arrived
|
|
376
|
-
|
|
377
|
-
Brief description of what was received.
|
|
378
|
-
|
|
379
|
-
## Why it matters
|
|
380
|
-
|
|
381
|
-
Why this intake item is important to process.
|
|
382
|
-
|
|
383
|
-
## How it should be ingested
|
|
384
|
-
|
|
385
|
-
Processing instructions:
|
|
386
|
-
- [ ] Step 1
|
|
387
|
-
- [ ] Step 2
|
|
388
|
-
- [ ] Step 3
|
|
389
|
-
|
|
390
|
-
## Notes
|
|
391
|
-
|
|
392
|
-
<!-- Caveats, access concerns, or cleanup notes. -->`;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
365
|
function writeIfMissing(filePath, content) {
|
|
396
366
|
if (fs.existsSync(filePath)) {
|
|
397
367
|
skipped.push(path.relative(target, filePath));
|
|
@@ -418,8 +388,6 @@ function copyLaneTemplates(laneKey) {
|
|
|
418
388
|
content = buildNotesTemplate();
|
|
419
389
|
} else if (templateName === '__meeting-notes-template__') {
|
|
420
390
|
content = buildMeetingNotesTemplate();
|
|
421
|
-
} else if (templateName === "__intake-template__") {
|
|
422
|
-
content = buildIntakeTemplate();
|
|
423
391
|
} else {
|
|
424
392
|
const templatePath = path.join(TEMPLATE_DIR, templateName);
|
|
425
393
|
if (fs.existsSync(templatePath)) {
|
|
@@ -543,8 +511,8 @@ function discoverProjectDirs(targetPath) {
|
|
|
543
511
|
// Extra inbox-style directories the user can opt into. These are NOT
|
|
544
512
|
// source-code, doc, or test directories: they're places where unsorted
|
|
545
513
|
// signals tend to land (downloads, scratch notes, exported research). The
|
|
546
|
-
//
|
|
547
|
-
//
|
|
514
|
+
// canonical `inbox/` at the project root is always watched and not listed
|
|
515
|
+
// here.
|
|
548
516
|
//
|
|
549
517
|
// Historically this list included `src/`, `docs/`, `tests/`, etc., and
|
|
550
518
|
// `init --yes` auto-enabled every one that existed in the project. That
|
|
@@ -555,10 +523,9 @@ function discoverProjectDirs(targetPath) {
|
|
|
555
523
|
const INTAKE_DIR_PRESETS = [];
|
|
556
524
|
|
|
557
525
|
async function askIntakeCollection(targetPath, skipInteractive) {
|
|
558
|
-
// Non-interactive default: empty parentDirs. The
|
|
559
|
-
//
|
|
560
|
-
//
|
|
561
|
-
// explicitly via `construct intake config set --add-dir=<path>`.
|
|
526
|
+
// Non-interactive default: empty parentDirs. The canonical `inbox/` zone
|
|
527
|
+
// handles every well-defined signal path. Users who want to watch additional
|
|
528
|
+
// directories opt in explicitly via `construct intake config set --add-dir=<path>`.
|
|
562
529
|
if (skipInteractive) {
|
|
563
530
|
return null;
|
|
564
531
|
}
|
|
@@ -583,7 +550,7 @@ async function askIntakeCollection(targetPath, skipInteractive) {
|
|
|
583
550
|
console.log(' INTAKE COLLECTION');
|
|
584
551
|
console.log('═══════════════════════════════════════════════════════════');
|
|
585
552
|
console.log('');
|
|
586
|
-
console.log('Intake watches
|
|
553
|
+
console.log('Intake watches `inbox/` at the project root.');
|
|
587
554
|
console.log('Add extra directories ONLY if signals (customer notes, exports,');
|
|
588
555
|
console.log('PDFs, etc.) regularly land there. Do not select code or finished');
|
|
589
556
|
console.log('artifact directories like src/, docs/, or tests/.');
|
|
@@ -599,7 +566,7 @@ async function askIntakeCollection(targetPath, skipInteractive) {
|
|
|
599
566
|
if (selected.length === 0) {
|
|
600
567
|
console.log('');
|
|
601
568
|
console.log('No extra directories selected. The inbox watcher will scan');
|
|
602
|
-
console.log('
|
|
569
|
+
console.log('`inbox/` only. Add more later with:');
|
|
603
570
|
console.log(' construct intake config set --add-dir=<path>');
|
|
604
571
|
console.log('');
|
|
605
572
|
return null;
|
|
@@ -866,41 +833,36 @@ async function main() {
|
|
|
866
833
|
|
|
867
834
|
|
|
868
835
|
// Detect existing project content once; the result feeds three decisions
|
|
869
|
-
// below: skip
|
|
870
|
-
// already covered elsewhere, and skip per-lane templates/ when
|
|
871
|
-
// templates/ already has them. --force bypasses every check.
|
|
836
|
+
// below: skip the inbox/ drop zone on custom intake, skip lane scaffolding
|
|
837
|
+
// for lanes already covered elsewhere, and skip per-lane templates/ when
|
|
838
|
+
// root templates/ already has them. --force bypasses every check.
|
|
872
839
|
|
|
873
840
|
const { detectExistingContent, shouldSkipProjectInbox, shouldScaffoldLane, formatDeferralSummary } =
|
|
874
841
|
await import('./init/detect-existing-structure.mjs');
|
|
875
842
|
const detection = detectExistingContent(target);
|
|
876
843
|
const inboxDecision = shouldSkipProjectInbox(detection, { force: forceScaffold });
|
|
877
844
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
// Intake-archetype scaffolding. When the active profile declares
|
|
890
|
-
// capabilities.intake.inbox, scaffold a project-root inbox/ drop zone and seed
|
|
891
|
-
// the dedup manifest. The whole inbox/ is ignored via host-disposition
|
|
892
|
-
// IGNORED_PATTERNS (ADR-0027 §1), so raw drops never enter source and no local
|
|
893
|
-
// keep-file is needed. Existing-structure detection already opted out of
|
|
894
|
-
// clobbering a user's pipeline above.
|
|
895
|
-
|
|
896
|
-
if (intakeCap?.inbox && !inboxDecision.skip) {
|
|
897
|
-
const projectInbox = path.join(target, 'inbox');
|
|
898
|
-
if (!fs.existsSync(projectInbox)) {
|
|
899
|
-
fs.mkdirSync(projectInbox, { recursive: true });
|
|
845
|
+
// Single canonical drop zone (ADR-0045 §C): the visible project-root `inbox/`.
|
|
846
|
+
// Raw drops stay out of source via host-disposition IGNORED_PATTERNS
|
|
847
|
+
// (ADR-0027 §1). Machine/runtime intake state lives under the gitignored
|
|
848
|
+
// `.cx/intake/` tree, created lazily by the queue on first use — init no
|
|
849
|
+
// longer scaffolds the deprecated `.cx/inbox/` drop zone. Skipped when a
|
|
850
|
+
// custom intake pipeline already owns the project root.
|
|
851
|
+
|
|
852
|
+
if (!inboxDecision.skip) {
|
|
853
|
+
const rootInbox = path.join(target, 'inbox');
|
|
854
|
+
if (!fs.existsSync(rootInbox)) {
|
|
855
|
+
fs.mkdirSync(rootInbox, { recursive: true });
|
|
900
856
|
created.push('inbox/');
|
|
901
857
|
}
|
|
902
858
|
|
|
903
|
-
|
|
859
|
+
// Writers assemble files under `inbox/.staging/` and atomically rename them
|
|
860
|
+
// into `inbox/`; the gitignored staging dir keeps a half-written drop
|
|
861
|
+
// invisible to the watcher until it lands.
|
|
862
|
+
|
|
863
|
+
fs.mkdirSync(path.join(rootInbox, '.staging'), { recursive: true });
|
|
864
|
+
|
|
865
|
+
if (intakeCap?.dedup === 'sha256') {
|
|
904
866
|
const { saveManifest, loadManifest, MANIFEST_REL_PATH } = await import('./intake/manifest.mjs');
|
|
905
867
|
const manifestExists = fs.existsSync(path.join(target, MANIFEST_REL_PATH));
|
|
906
868
|
if (!manifestExists) {
|
|
@@ -908,6 +870,9 @@ async function main() {
|
|
|
908
870
|
created.push(MANIFEST_REL_PATH);
|
|
909
871
|
}
|
|
910
872
|
}
|
|
873
|
+
} else {
|
|
874
|
+
console.log(`[init:intake] skipping inbox/ — ${inboxDecision.reason}. Run with --force to scaffold anyway.`);
|
|
875
|
+
skipped.push('inbox/ (deferred to existing intake)');
|
|
911
876
|
}
|
|
912
877
|
|
|
913
878
|
// Gitignore every Construct-generated artifact whose disposition is `ignored`
|
|
@@ -1074,32 +1039,22 @@ async function main() {
|
|
|
1074
1039
|
console.warn(`⚠️ Could not inject agent-instruction block: ${err.message}`);
|
|
1075
1040
|
}
|
|
1076
1041
|
|
|
1077
|
-
// Intake
|
|
1078
|
-
//
|
|
1079
|
-
//
|
|
1080
|
-
//
|
|
1042
|
+
// Intake policy — write the single-zone model (ADR-0045 §C) so the user can
|
|
1043
|
+
// inspect what's watched and edit it directly. The canonical `inbox/` at the
|
|
1044
|
+
// project root is the only drop zone; empty parentDirs is correct — extra
|
|
1045
|
+
// dirs are opt-in.
|
|
1081
1046
|
|
|
1082
1047
|
const intakeConfig = (await askIntakeCollection(target, skipInteractive)) ?? { parentDirs: [], maxDepth: 4 };
|
|
1083
|
-
// When a custom intake surface was detected, default includeProjectInbox
|
|
1084
|
-
// to false so the watcher does not double-claim with the user's existing
|
|
1085
|
-
// pipeline. User can opt in later via `construct config intake.includeProjectInbox=true`.
|
|
1086
|
-
|
|
1087
|
-
if (inboxDecision.skip) intakeConfig.includeProjectInbox = false;
|
|
1088
1048
|
|
|
1089
|
-
|
|
1090
|
-
// the project-root inbox/ drop zone alongside .cx/inbox/. Mirrors the
|
|
1091
|
-
// existing includeProjectInbox / includeDocsIntake toggles rather than
|
|
1092
|
-
// polluting parentDirs (which is reserved for user-explicit dirs).
|
|
1093
|
-
|
|
1094
|
-
if (intakeCap?.inbox) {
|
|
1095
|
-
intakeConfig.includeArchetypeInbox = true;
|
|
1096
|
-
}
|
|
1097
|
-
const { saveIntakeConfig } = await import('./intake/intake-config.mjs');
|
|
1049
|
+
const { saveIntakePolicy } = await import('./config/intake-policy.mjs');
|
|
1098
1050
|
try {
|
|
1099
|
-
|
|
1100
|
-
|
|
1051
|
+
saveIntakePolicy(target, {
|
|
1052
|
+
maxDepth: intakeConfig.maxDepth,
|
|
1053
|
+
additionalDirs: intakeConfig.parentDirs ?? [],
|
|
1054
|
+
});
|
|
1055
|
+
created.push('construct.config.json (intakePolicy)');
|
|
1101
1056
|
} catch (err) {
|
|
1102
|
-
console.warn(`⚠️ Could not write intake
|
|
1057
|
+
console.warn(`⚠️ Could not write intake policy: ${err.message}`);
|
|
1103
1058
|
}
|
|
1104
1059
|
|
|
1105
1060
|
// Ask about documentation system
|
|
@@ -1114,7 +1069,7 @@ async function main() {
|
|
|
1114
1069
|
// Create documentation system if lanes specified
|
|
1115
1070
|
if (lanes.length > 0) {
|
|
1116
1071
|
// Filter out lanes that the project already covers elsewhere
|
|
1117
|
-
// (issue #97: don't create docs/meetings/ when internal/meetings/
|
|
1072
|
+
// (issue #97: don't create docs/notes/meetings/ when internal/meetings/
|
|
1118
1073
|
// has 12 markdown files). --force bypasses the filter.
|
|
1119
1074
|
|
|
1120
1075
|
const deferredLanes = [];
|
|
@@ -1209,7 +1164,7 @@ async function main() {
|
|
|
1209
1164
|
const seed = await syncFileStateToSql(target, { env: process.env, project: projectName });
|
|
1210
1165
|
if (seed?.status === 'ok' && (seed.documentsSynced || 0) > 0) {
|
|
1211
1166
|
console.log(`\n🔍 Indexed existing project material: ${seed.documentsSynced} doc(s), ${seed.embeddingsSynced || 0} embeddings.`);
|
|
1212
|
-
console.log(` The agent can now compare new intake (
|
|
1167
|
+
console.log(` The agent can now compare new intake (inbox/) against your existing PRDs, ADRs, and notes.`);
|
|
1213
1168
|
}
|
|
1214
1169
|
} catch { /* silent — corpus seeding is best-effort, not a setup blocker */ }
|
|
1215
1170
|
|
|
@@ -1240,9 +1195,9 @@ async function main() {
|
|
|
1240
1195
|
console.log(`${++step}. Edit plan.md`);
|
|
1241
1196
|
console.log(' Add your current work and tasks');
|
|
1242
1197
|
console.log('');
|
|
1243
|
-
if (
|
|
1198
|
+
if (created.includes('inbox/')) {
|
|
1244
1199
|
console.log(`${++step}. Use Intake`);
|
|
1245
|
-
console.log(' Drop files in
|
|
1200
|
+
console.log(' Drop files in inbox/ for processing');
|
|
1246
1201
|
console.log(' Run: construct intake');
|
|
1247
1202
|
console.log('');
|
|
1248
1203
|
}
|