@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
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* @exits 0 = pass
|
|
13
13
|
*/
|
|
14
14
|
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
15
|
-
import { homedir } from 'os';
|
|
16
15
|
import { join } from 'path';
|
|
17
16
|
import { readContextState, writeContextState } from '../context-state.mjs';
|
|
17
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
18
18
|
|
|
19
19
|
const CONTEXT_LIMIT_PATTERNS = [
|
|
20
20
|
'context window', 'maximum context', 'too long', 'prompt is too long',
|
|
@@ -23,7 +23,7 @@ const CONTEXT_LIMIT_PATTERNS = [
|
|
|
23
23
|
];
|
|
24
24
|
|
|
25
25
|
const COOLDOWN_MS = 10 * 60 * 1000;
|
|
26
|
-
const STATE_PATH = join(
|
|
26
|
+
const STATE_PATH = join(doctorRoot(), 'context-recovery.json');
|
|
27
27
|
|
|
28
28
|
let input = {};
|
|
29
29
|
try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
|
|
@@ -42,7 +42,7 @@ try {
|
|
|
42
42
|
if (now - (state.lastTriggeredAt || 0) < COOLDOWN_MS) process.exit(0);
|
|
43
43
|
} catch { /* first run */ }
|
|
44
44
|
|
|
45
|
-
const cxDir =
|
|
45
|
+
const cxDir = doctorRoot();
|
|
46
46
|
try { mkdirSync(cxDir, { recursive: true }); } catch { /* exists */ }
|
|
47
47
|
try { writeFileSync(STATE_PATH, JSON.stringify({ lastTriggeredAt: now })); } catch { /* best effort */ }
|
|
48
48
|
|
|
@@ -50,7 +50,7 @@ const cwd = input?.cwd || process.cwd();
|
|
|
50
50
|
|
|
51
51
|
let existingContext = '';
|
|
52
52
|
try {
|
|
53
|
-
const state = readContextState(cwd) || readContextState(
|
|
53
|
+
const state = readContextState(cwd) || readContextState(doctorRoot());
|
|
54
54
|
if (state?.markdown) existingContext = String(state.markdown).slice(0, 800);
|
|
55
55
|
else if (state?.context) existingContext = String(state.context).slice(0, 800);
|
|
56
56
|
} catch { /* none */ }
|
|
@@ -66,7 +66,7 @@ const recoveryContext = [
|
|
|
66
66
|
const projectCxDir = join(cwd, '.cx');
|
|
67
67
|
try { mkdirSync(projectCxDir, { recursive: true }); } catch { /* exists */ }
|
|
68
68
|
try { writeContextState(cwd, { source: 'context-window-recovery', recoveryContext, format: 'json' }, { markdown: recoveryContext }); } catch { /* best effort */ }
|
|
69
|
-
try { writeContextState(
|
|
69
|
+
try { writeContextState(doctorRoot(), { source: 'context-window-recovery', recoveryContext, format: 'json' }, { markdown: recoveryContext }); } catch { /* best effort */ }
|
|
70
70
|
|
|
71
71
|
process.stdout.write([
|
|
72
72
|
``,
|
package/lib/hooks/dep-audit.mjs
CHANGED
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { readFileSync, existsSync } from 'node:fs';
|
|
17
17
|
import { execSync } from 'node:child_process';
|
|
18
|
-
import { homedir } from 'node:os';
|
|
19
18
|
import { join, dirname, basename } from 'node:path';
|
|
20
19
|
import { appendBounded } from '../logging/rotate.mjs';
|
|
21
20
|
import { logHookFailure } from './_lib/log.mjs';
|
|
21
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
22
22
|
|
|
23
23
|
const filePath = process.env.TOOL_INPUT_FILE_PATH || '';
|
|
24
24
|
if (!filePath) process.exit(0);
|
|
@@ -31,7 +31,7 @@ if (!MANIFESTS.has(file)) process.exit(0);
|
|
|
31
31
|
if (/node_modules|\/dist\/|\/build\/|\/\.git\//.test(filePath)) process.exit(0);
|
|
32
32
|
|
|
33
33
|
const projectDir = dirname(filePath);
|
|
34
|
-
const warnFlagsPath = join(
|
|
34
|
+
const warnFlagsPath = join(doctorRoot(), 'warn-flags.txt');
|
|
35
35
|
|
|
36
36
|
function appendWarn(msg) {
|
|
37
37
|
try { appendBounded('bash-warn-flags', warnFlagsPath, msg + '\n'); }
|
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
22
|
-
import { homedir } from 'node:os';
|
|
23
22
|
import { join, dirname } from 'node:path';
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
25
|
+
|
|
26
|
+
const STATE_PATH = join(doctorRoot(), 'doc-coupling.json');
|
|
26
27
|
const CODE_RE = /^(lib|bin|src|app)\/.*\.(?:m?js|jsx?|tsx?|cjs)$/;
|
|
27
28
|
const DOC_RE = /^(?:CHANGELOG\.md|docs\/.+\.md|\.cx\/context\.(?:md|json))$/;
|
|
28
29
|
const THRESHOLDS = [3, 5, 10];
|
|
@@ -20,6 +20,7 @@ import { homedir } from 'node:os';
|
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
import { appendBounded } from '../logging/rotate.mjs';
|
|
22
22
|
import { logHookFailure } from './_lib/log.mjs';
|
|
23
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
23
24
|
|
|
24
25
|
const TS_EXTS = new Set(['.ts','.tsx','.js','.mjs','.jsx','.cjs']);
|
|
25
26
|
const filePath = process.env.TOOL_INPUT_FILE_PATH || '';
|
|
@@ -28,7 +29,7 @@ if (!filePath) process.exit(0);
|
|
|
28
29
|
|
|
29
30
|
const ext = path.extname(filePath);
|
|
30
31
|
const home = homedir();
|
|
31
|
-
const countPath = path.join(home, '
|
|
32
|
+
const countPath = path.join(doctorRoot(home), 'files-changed-count.txt');
|
|
32
33
|
|
|
33
34
|
// Always increment the total files-changed counter for notify
|
|
34
35
|
try {
|
|
@@ -41,7 +42,7 @@ try {
|
|
|
41
42
|
// Only accumulate TS/JS files for typecheck
|
|
42
43
|
if (!TS_EXTS.has(ext)) process.exit(0);
|
|
43
44
|
|
|
44
|
-
const tcPath = path.join(home, '
|
|
45
|
+
const tcPath = path.join(doctorRoot(home), 'pending-typecheck.txt');
|
|
45
46
|
try {
|
|
46
47
|
const existing = existsSync(tcPath) ? readFileSync(tcPath, 'utf8').split('\n').filter(Boolean) : [];
|
|
47
48
|
if (!existing.includes(filePath)) {
|
package/lib/hooks/edit-guard.mjs
CHANGED
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
import { readFileSync, existsSync } from 'fs';
|
|
15
15
|
import { createHash } from 'crypto';
|
|
16
16
|
import { join, resolve } from 'path';
|
|
17
|
-
import { homedir } from 'os';
|
|
18
17
|
import { logHookFailure } from './_lib/log.mjs';
|
|
18
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
19
19
|
|
|
20
|
-
const HASH_STORE = join(
|
|
20
|
+
const HASH_STORE = join(doctorRoot(), 'file-hashes.json');
|
|
21
21
|
|
|
22
22
|
function storedHash(absPath) {
|
|
23
23
|
try {
|
|
@@ -48,7 +48,7 @@ function absOf(p) { return p.startsWith('/') ? p : resolve(cwd, p); }
|
|
|
48
48
|
|
|
49
49
|
if ((toolName === 'Edit' || toolName === 'Write' || toolName === 'MultiEdit') && process.env.CONSTRUCT_ROLES !== 'off') {
|
|
50
50
|
try {
|
|
51
|
-
const lastAgentPath = join(
|
|
51
|
+
const lastAgentPath = join(doctorRoot(), 'last-agent.json');
|
|
52
52
|
if (existsSync(lastAgentPath)) {
|
|
53
53
|
const last = JSON.parse(readFileSync(lastAgentPath, 'utf8'));
|
|
54
54
|
const lastTs = last?.ts ? Date.parse(last.ts) : 0;
|
package/lib/hooks/guard-bash.mjs
CHANGED
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { createInterface } from 'readline';
|
|
15
15
|
import { readFileSync, existsSync } from 'fs';
|
|
16
|
-
import { homedir } from 'os';
|
|
17
16
|
import { join } from 'path';
|
|
18
17
|
import { logHookFailure } from './_lib/log.mjs';
|
|
18
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
19
19
|
|
|
20
20
|
const BLOCK_PATTERNS = [
|
|
21
21
|
{
|
|
@@ -99,7 +99,7 @@ for (const { pattern, reason } of WARN_PATTERNS) {
|
|
|
99
99
|
|
|
100
100
|
if (process.env.CONSTRUCT_ROLES !== 'off') {
|
|
101
101
|
try {
|
|
102
|
-
const cxDir =
|
|
102
|
+
const cxDir = doctorRoot();
|
|
103
103
|
const id = String(process.env.CONSTRUCT_AGENT_ID || '').replace(/^cx-/, '');
|
|
104
104
|
let agentData = null;
|
|
105
105
|
|
|
@@ -32,8 +32,9 @@ import {
|
|
|
32
32
|
writeProviderCooldown,
|
|
33
33
|
} from '../model-router.mjs';
|
|
34
34
|
import { readHookInput } from './_lib/input.mjs';
|
|
35
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
35
36
|
|
|
36
|
-
const cooldownPath = join(
|
|
37
|
+
const cooldownPath = join(doctorRoot(), 'provider-cooldowns.json');
|
|
37
38
|
const envPath = join(process.cwd(), '.env');
|
|
38
39
|
|
|
39
40
|
const input = readHookInput();
|
|
@@ -24,6 +24,7 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
|
24
24
|
import { execSync } from 'node:child_process';
|
|
25
25
|
import { join } from 'node:path';
|
|
26
26
|
import { homedir } from 'node:os';
|
|
27
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
27
28
|
|
|
28
29
|
let input = {};
|
|
29
30
|
try { input = JSON.parse(readFileSync(0, 'utf8')) || {}; } catch { process.exit(0); }
|
|
@@ -55,7 +56,7 @@ if (hookEvent === 'PreToolUse') {
|
|
|
55
56
|
const READ_TOOLS = new Set(['Read', 'Grep', 'Glob', 'LS', 'NotebookRead']);
|
|
56
57
|
const BLOCKED_TOOLS = new Set(['Write', 'Edit', 'MultiEdit', 'NotebookEdit', 'TodoWrite']);
|
|
57
58
|
|
|
58
|
-
const stateDir =
|
|
59
|
+
const stateDir = doctorRoot(home);
|
|
59
60
|
const statePath = join(stateDir, 'bootstrap-state.json');
|
|
60
61
|
const sessionId = input.session_id || input.sessionId || 'default';
|
|
61
62
|
|
|
@@ -147,7 +148,7 @@ if (hookEvent === 'Stop') {
|
|
|
147
148
|
|
|
148
149
|
const branch = safeExec('git branch --show-current');
|
|
149
150
|
const isFeatureBranch = branch && branch !== 'main' && branch !== 'dev' && branch !== 'master';
|
|
150
|
-
const filesChangedPath = join(home, '
|
|
151
|
+
const filesChangedPath = join(doctorRoot(home), 'files-changed-count.txt');
|
|
151
152
|
let filesChanged = 0;
|
|
152
153
|
try { filesChanged = parseInt(readFileSync(filesChangedPath, 'utf8').trim() || '0', 10); } catch {}
|
|
153
154
|
|
|
@@ -188,7 +189,7 @@ if (hookEvent === 'Stop') {
|
|
|
188
189
|
}
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
const driveSessionPath = join(home, '
|
|
192
|
+
const driveSessionPath = join(doctorRoot(home), 'drive-session.json');
|
|
192
193
|
if (existsSync(driveSessionPath)) {
|
|
193
194
|
let ds = {};
|
|
194
195
|
try { ds = JSON.parse(readFileSync(driveSessionPath, 'utf8')); } catch {}
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
* @exits 0 = pass
|
|
18
18
|
*/
|
|
19
19
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
20
|
-
import { homedir } from 'os';
|
|
21
20
|
import { join } from 'path';
|
|
22
21
|
import { writeContextState, contextMarkdownPath, contextJsonPath } from '../context-state.mjs';
|
|
23
22
|
import { flushReadTrackerDeltas } from '../read-tracker-store.mjs';
|
|
23
|
+
import { doctorRoot } from '../config/xdg.mjs';
|
|
24
24
|
|
|
25
25
|
// Section headings that are auto-generated by this hook. Any other heading in
|
|
26
26
|
// the existing context.md is treated as manually authored and preserved.
|
|
@@ -100,8 +100,8 @@ const transcriptPath = input?.transcript_path || '';
|
|
|
100
100
|
const filesChanged = [];
|
|
101
101
|
const decisions = [];
|
|
102
102
|
const pendingTodos = [];
|
|
103
|
-
const warnFlagsPath = join(
|
|
104
|
-
const efficiencyPath = join(
|
|
103
|
+
const warnFlagsPath = join(doctorRoot(), 'warn-flags.txt');
|
|
104
|
+
const efficiencyPath = join(doctorRoot(), 'session-efficiency.json');
|
|
105
105
|
let lastSummary = '';
|
|
106
106
|
|
|
107
107
|
if (transcriptPath && existsSync(transcriptPath)) {
|
|
@@ -275,12 +275,12 @@ if (decisions.length) {
|
|
|
275
275
|
try { writeFileSync(join(decisionsDir, `${slug}.md`), decisionDoc); } catch { /* best effort */ }
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
const globalCxDir = join(
|
|
278
|
+
const globalCxDir = join(doctorRoot(), '.cx');
|
|
279
279
|
try { mkdirSync(globalCxDir, { recursive: true }); } catch { /* exists */ }
|
|
280
|
-
const globalMdPath = contextMarkdownPath(
|
|
280
|
+
const globalMdPath = contextMarkdownPath(doctorRoot());
|
|
281
281
|
const globalManualSections = extractManualSections(globalMdPath);
|
|
282
282
|
const globalFinalContent = content + (globalManualSections ? '\n' + globalManualSections : '');
|
|
283
|
-
const globalMergedJson = mergeExistingJson(contextJsonPath(
|
|
284
|
-
try { writeContextState(
|
|
283
|
+
const globalMergedJson = mergeExistingJson(contextJsonPath(doctorRoot()), contextJson);
|
|
284
|
+
try { writeContextState(doctorRoot(), { ...globalMergedJson, contextSummary: lastSummary }, { markdown: globalFinalContent }); } catch { /* best effort */ }
|
|
285
285
|
|
|
286
286
|
process.exit(0);
|
|
@@ -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) : [];
|
|
@@ -400,7 +401,7 @@ try {
|
|
|
400
401
|
try {
|
|
401
402
|
const sessionId = input?.session_id || input?.sessionId;
|
|
402
403
|
if (sessionId) {
|
|
403
|
-
const bootstrapPath = join(
|
|
404
|
+
const bootstrapPath = join(doctorRoot(), "bootstrap-state.json");
|
|
404
405
|
let state = {};
|
|
405
406
|
try { state = JSON.parse(readFileSync(bootstrapPath, "utf8")) || {}; } catch {}
|
|
406
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) {
|