@geraldmaron/construct 1.2.1 → 1.2.3
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/apps/chat/engine/ai-sdk-agent.mjs +183 -0
- package/apps/chat/engine/loop-driver.mjs +211 -0
- package/apps/chat/engine/models.mjs +122 -0
- package/apps/chat/engine/provider-adapters.mjs +171 -0
- package/apps/chat/engine/tools/permission.mjs +54 -0
- package/apps/chat/engine/tools/primitives.mjs +180 -0
- package/apps/chat/engine/tools/registry.mjs +122 -0
- package/apps/chat/engine/turn-controls.mjs +70 -0
- package/bin/construct +93 -79
- 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 +4 -6
- 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/legacy-config-migration.mjs +59 -0
- 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-env.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 +21 -11
- 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 +4 -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
package/bin/construct
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
PROJECT_CONFIG_FILENAME,
|
|
39
39
|
} from '../lib/config/project-config.mjs';
|
|
40
40
|
import { validateProjectConfig } from '../lib/config/schema.mjs';
|
|
41
|
+
import { configDir, doctorRoot } from '../lib/config/xdg.mjs';
|
|
41
42
|
import {
|
|
42
43
|
DEPLOYMENT_MODES,
|
|
43
44
|
DEPLOYMENT_MODE_ENV_KEY,
|
|
@@ -400,7 +401,7 @@ async function cmdDoctor() {
|
|
|
400
401
|
}
|
|
401
402
|
|
|
402
403
|
// .env files
|
|
403
|
-
for (const envPath of [join(process.cwd(), '.env'), join(homedir(), '.env'),
|
|
404
|
+
for (const envPath of [join(process.cwd(), '.env'), join(homedir(), '.env'), getUserEnvPath(homedir())]) {
|
|
404
405
|
if (fs.existsSync(envPath)) {
|
|
405
406
|
try {
|
|
406
407
|
const content = fs.readFileSync(envPath, 'utf8');
|
|
@@ -592,7 +593,7 @@ async function cmdDoctor() {
|
|
|
592
593
|
// minute at 50MB, but a freshly-upgraded install with a 34GB legacy
|
|
593
594
|
// log shouldn't wait 60s. --fix-embed-log forces rotation now.
|
|
594
595
|
|
|
595
|
-
const embedLogPath = path.join(userHome, '
|
|
596
|
+
const embedLogPath = path.join(doctorRoot(userHome), 'runtime', 'embed-daemon.log');
|
|
596
597
|
if (fs.existsSync(embedLogPath)) {
|
|
597
598
|
const sizeMb = fs.statSync(embedLogPath).size / 1024 / 1024;
|
|
598
599
|
const FORCE_THRESHOLD_MB = Number(process.env.CONSTRUCT_DOCTOR_EMBED_LOG_FORCE_MB) || 500;
|
|
@@ -627,11 +628,11 @@ async function cmdDoctor() {
|
|
|
627
628
|
'intent-verifications.jsonl',
|
|
628
629
|
];
|
|
629
630
|
const legacyState = projectScopedNames
|
|
630
|
-
.map((name) => ({ name, path: path.join(userHome,
|
|
631
|
+
.map((name) => ({ name, path: path.join(doctorRoot(userHome), name) }))
|
|
631
632
|
.filter((p) => fs.existsSync(p.path) && fs.statSync(p.path).size > 0);
|
|
632
633
|
|
|
633
634
|
if (legacyState.length > 0 && wantsFixMigrateState) {
|
|
634
|
-
const archiveDir = path.join(userHome, '
|
|
635
|
+
const archiveDir = path.join(doctorRoot(userHome), 'legacy');
|
|
635
636
|
fs.mkdirSync(archiveDir, { recursive: true });
|
|
636
637
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
637
638
|
const moved = [];
|
|
@@ -652,6 +653,21 @@ async function cmdDoctor() {
|
|
|
652
653
|
);
|
|
653
654
|
}
|
|
654
655
|
|
|
656
|
+
// Mirror model tier overrides stranded in the pre-XDG legacy config forward
|
|
657
|
+
// before resolving tiers, so an install upgraded across the XDG move reports
|
|
658
|
+
// configured without a manual copy. Applied to process.env too so the check
|
|
659
|
+
// below sees the just-migrated values in the same run.
|
|
660
|
+
try {
|
|
661
|
+
const { migrateLegacyModelConfig } = await import('../lib/config/legacy-config-migration.mjs');
|
|
662
|
+
const migration = migrateLegacyModelConfig({ homeDir: HOME });
|
|
663
|
+
if (migration.performed) {
|
|
664
|
+
Object.assign(process.env, migration.migrated);
|
|
665
|
+
println(` fix: migrated ${Object.keys(migration.migrated).join(', ')} from ${migration.legacyPath} → ${migration.xdgPath}`);
|
|
666
|
+
}
|
|
667
|
+
} catch (err) {
|
|
668
|
+
add(`Models — legacy config migration failed: ${err.message}`, false, true);
|
|
669
|
+
}
|
|
670
|
+
|
|
655
671
|
// Tier model selection. Construct ships with no default — at least
|
|
656
672
|
// one tier must be configured (registry.json primary OR CX_MODEL_*
|
|
657
673
|
// env override) before any LLM-backed workflow can run.
|
|
@@ -739,13 +755,13 @@ async function cmdDoctor() {
|
|
|
739
755
|
const vscodeSettingsOk = vscodeSettingsPaths.some((candidate) => fs.existsSync(candidate));
|
|
740
756
|
if (vscodeSettingsOk) add('VS Code settings file', true, true);
|
|
741
757
|
}
|
|
742
|
-
add('User config ready', fs.existsSync(getUserEnvPath(HOME)) || fs.existsSync(
|
|
758
|
+
add('User config ready', fs.existsSync(getUserEnvPath(HOME)) || fs.existsSync(configDir(HOME)), true);
|
|
743
759
|
add('skills/ directory', fs.existsSync(path.join(ROOT_DIR, 'skills')));
|
|
744
760
|
add('rules/common/', fs.existsSync(path.join(ROOT_DIR, 'rules', 'common')));
|
|
745
761
|
add('specialists/prompts/ directory', fs.existsSync(path.join(ROOT_DIR, 'specialists', 'prompts')));
|
|
746
762
|
add('Hybrid storage env keys documented', fs.existsSync(path.join(ROOT_DIR, '.env.example')));
|
|
747
|
-
fs.mkdirSync(path.join(
|
|
748
|
-
add('
|
|
763
|
+
fs.mkdirSync(path.join(doctorRoot(), 'performance-reviews'), { recursive: true });
|
|
764
|
+
add('performance-reviews/ ready', true);
|
|
749
765
|
|
|
750
766
|
// construct.config.json — optional today (config is permitted to be absent;
|
|
751
767
|
// env vars + defaults still work). The check enforces that when the file
|
|
@@ -765,33 +781,29 @@ async function cmdDoctor() {
|
|
|
765
781
|
|
|
766
782
|
// Claude Code Stop-hook installation check. The settings template
|
|
767
783
|
// wires several Stop hooks (policy-engine / stop-typecheck /
|
|
768
|
-
// stop-notify / readme-age-check)
|
|
769
|
-
//
|
|
770
|
-
//
|
|
771
|
-
//
|
|
772
|
-
//
|
|
773
|
-
// command away.
|
|
784
|
+
// stop-notify / readme-age-check) whose commands sync resolves to the
|
|
785
|
+
// hook lib symlink. If those scripts don't resolve, every Stop hook
|
|
786
|
+
// fails silently — including stop-notify, which is what appends session
|
|
787
|
+
// cost data. Result: the dashboard stops updating and the user sees stale
|
|
788
|
+
// data. Surface loudly so the fix is one command away.
|
|
774
789
|
try {
|
|
775
790
|
const settingsPath = path.join(HOME, '.claude', 'settings.json');
|
|
776
791
|
if (fs.existsSync(settingsPath)) {
|
|
777
792
|
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
778
793
|
const stopHooks = settings?.hooks?.Stop || [];
|
|
779
|
-
const installRoot = path.join(HOME, '
|
|
780
|
-
const installed = fs.existsSync(installRoot);
|
|
794
|
+
const installRoot = path.join(configDir(HOME), 'lib', 'hooks');
|
|
781
795
|
const referenced = [];
|
|
782
796
|
for (const group of stopHooks) {
|
|
783
797
|
for (const h of (group?.hooks || [])) {
|
|
784
798
|
const cmd = String(h?.command || '');
|
|
785
|
-
const m = cmd.match(
|
|
799
|
+
const m = cmd.match(/(?:^|["\s])(\/[^"\s]*\/lib\/hooks\/[a-zA-Z0-9_-]+\.mjs)/);
|
|
786
800
|
if (m) referenced.push(m[1]);
|
|
787
801
|
}
|
|
788
802
|
}
|
|
789
|
-
const missing =
|
|
790
|
-
? referenced.filter((f) => !fs.existsSync(path.join(installRoot, f)))
|
|
791
|
-
: referenced;
|
|
803
|
+
const missing = referenced.filter((scriptPath) => !fs.existsSync(scriptPath));
|
|
792
804
|
if (referenced.length > 0 && missing.length > 0) {
|
|
793
805
|
add(
|
|
794
|
-
`Claude Stop hooks broken: ${missing.length} hook${missing.length === 1 ? '' : 's'}
|
|
806
|
+
`Claude Stop hooks broken: ${missing.length} hook${missing.length === 1 ? '' : 's'} do not resolve. Re-run \`construct install --scope=user\` (symlinks ${ROOT_DIR}/lib → ${installRoot.replace(/\/hooks$/, '')}).`,
|
|
795
807
|
false,
|
|
796
808
|
false,
|
|
797
809
|
);
|
|
@@ -1073,7 +1085,7 @@ async function cmdDoctor() {
|
|
|
1073
1085
|
}
|
|
1074
1086
|
|
|
1075
1087
|
try {
|
|
1076
|
-
const failuresPath = path.join(
|
|
1088
|
+
const failuresPath = path.join(doctorRoot(), 'hook-failures.jsonl');
|
|
1077
1089
|
if (fs.existsSync(failuresPath)) {
|
|
1078
1090
|
const lines = fs.readFileSync(failuresPath, 'utf8').trim().split('\n').filter(Boolean);
|
|
1079
1091
|
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
|
@@ -1519,7 +1531,7 @@ async function cmdMigrate(args) {
|
|
|
1519
1531
|
}
|
|
1520
1532
|
|
|
1521
1533
|
async function cmdReview(args) {
|
|
1522
|
-
fs.mkdirSync(path.join(
|
|
1534
|
+
fs.mkdirSync(path.join(doctorRoot(), 'performance-reviews'), { recursive: true });
|
|
1523
1535
|
const sub = args[0];
|
|
1524
1536
|
|
|
1525
1537
|
if (!sub || sub === 'run') {
|
|
@@ -2018,7 +2030,7 @@ async function _cmdPricing(args) {
|
|
|
2018
2030
|
const f = describePricingCatalogFreshness();
|
|
2019
2031
|
if (!f.present) { info(f.message); return; }
|
|
2020
2032
|
try {
|
|
2021
|
-
const cached = JSON.parse(fs.readFileSync(path.join(
|
|
2033
|
+
const cached = JSON.parse(fs.readFileSync(path.join(doctorRoot(), 'pricing-cache.json'), 'utf8'));
|
|
2022
2034
|
const divergences = checkPricingDivergence(cached.models || []);
|
|
2023
2035
|
if (divergences.length === 0) { info('No pricing divergences (>25%) between LiteLLM and the static fallback.'); return; }
|
|
2024
2036
|
println(`⚠ ${divergences.length} divergence(s):`);
|
|
@@ -2116,15 +2128,13 @@ async function printIntakeHelp() {
|
|
|
2116
2128
|
println(' construct intake metrics Show pipeline volume, velocity, and processing stats.');
|
|
2117
2129
|
println(' construct intake integrate <id> <system> Push intake to external system (github, jira, confluence).');
|
|
2118
2130
|
println(' construct intake integrate <id> <system> --create-issue Create an issue from the intake packet.');
|
|
2119
|
-
println(' construct intake config Show
|
|
2131
|
+
println(' construct intake config Show the inbox/ drop zone + scan depth.');
|
|
2120
2132
|
println(' construct intake config set --depth=N [--add-dir=PATH] [--remove-dir=PATH]');
|
|
2133
|
+
println(' Update intake config. --depth caps at the hard limit.');
|
|
2121
2134
|
println(' construct intake extraction-warnings [--since=7d] [--kind=table] [--limit=50]');
|
|
2122
2135
|
println(' Summarize ~/.cx/intake/extraction-warnings.jsonl (dropped content log).');
|
|
2123
2136
|
println(' construct intake needs-asr list List audio/video files awaiting ASR transcription.');
|
|
2124
2137
|
println(' construct intake needs-asr show <id> Show a specific ASR-pending packet.');
|
|
2125
|
-
println(' Update intake config. --depth caps at the hard limit.');
|
|
2126
|
-
println(' construct intake config set --include-project-inbox=on|off');
|
|
2127
|
-
println(' construct intake config set --include-docs-intake=on|off');
|
|
2128
2138
|
}
|
|
2129
2139
|
|
|
2130
2140
|
function parseKvFlags(args, keys) {
|
|
@@ -2618,7 +2628,6 @@ async function cmdIntakeReroute(args, cwd) {
|
|
|
2618
2628
|
|
|
2619
2629
|
async function cmdIntakeExtractionWarnings(args) {
|
|
2620
2630
|
const { createReadStream, existsSync } = await import('node:fs');
|
|
2621
|
-
const { homedir } = await import('node:os');
|
|
2622
2631
|
const { join } = await import('node:path');
|
|
2623
2632
|
const { createInterface } = await import('node:readline');
|
|
2624
2633
|
|
|
@@ -2636,7 +2645,7 @@ async function cmdIntakeExtractionWarnings(args) {
|
|
|
2636
2645
|
}
|
|
2637
2646
|
}
|
|
2638
2647
|
|
|
2639
|
-
const logPath = join(
|
|
2648
|
+
const logPath = join(doctorRoot(), 'intake', 'extraction-warnings.jsonl');
|
|
2640
2649
|
if (!existsSync(logPath)) {
|
|
2641
2650
|
println('No extraction warnings recorded yet.');
|
|
2642
2651
|
return;
|
|
@@ -2687,11 +2696,10 @@ async function cmdIntakeExtractionWarnings(args) {
|
|
|
2687
2696
|
|
|
2688
2697
|
async function cmdIntakeNeedsAsr(args) {
|
|
2689
2698
|
const { existsSync, readdirSync, readFileSync } = await import('node:fs');
|
|
2690
|
-
const { homedir } = await import('node:os');
|
|
2691
2699
|
const { join } = await import('node:path');
|
|
2692
2700
|
|
|
2693
2701
|
const sub = args[0] || 'list';
|
|
2694
|
-
const asrDir = join(
|
|
2702
|
+
const asrDir = join(doctorRoot(), 'intake', 'needs-asr');
|
|
2695
2703
|
|
|
2696
2704
|
if (sub === 'list') {
|
|
2697
2705
|
if (!existsSync(asrDir)) { println('No audio/video files awaiting ASR.'); return; }
|
|
@@ -2856,33 +2864,23 @@ async function cmdIntakeConfig(args) {
|
|
|
2856
2864
|
const cwd = process.cwd();
|
|
2857
2865
|
|
|
2858
2866
|
const action = args[0];
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
if (!result.migrated) {
|
|
2863
|
-
info(result.reason ?? 'nothing to migrate');
|
|
2864
|
-
return;
|
|
2865
|
-
}
|
|
2866
|
-
info('Migrated .cx/intake-config.json → construct.config.json intakePolicy');
|
|
2867
|
-
println(JSON.stringify(result.policy, null, 2));
|
|
2868
|
-
return;
|
|
2869
|
-
}
|
|
2867
|
+
|
|
2868
|
+
// Single-zone model (ADR-0045 §C): the canonical `inbox/` is always watched.
|
|
2869
|
+
// Only maxDepth and additionalDirs are configurable.
|
|
2870
2870
|
|
|
2871
2871
|
if (!action || action === 'show') {
|
|
2872
2872
|
const cfg = loadIntakeConfig(cwd, process.env);
|
|
2873
2873
|
const desc = describeIntakeDepth(cfg.maxDepth);
|
|
2874
2874
|
println(`Intake config (root: ${cwd})`);
|
|
2875
2875
|
println('');
|
|
2876
|
-
println(
|
|
2877
|
-
println(` includeRootInbox: ${(cfg.includeRootInbox ?? cfg.includeArchetypeInbox) ? 'on' : 'off'} (inbox/)`);
|
|
2878
|
-
println(` includeDocsIntake: ${cfg.includeDocsIntake ? 'on' : 'off'} (docs/intake)`);
|
|
2876
|
+
println(' drop zone: inbox/ (always watched)');
|
|
2879
2877
|
println(` maxDepth: ${cfg.maxDepth} — ${desc.label}`);
|
|
2880
2878
|
println(` ${desc.detail}`);
|
|
2881
2879
|
println(` hardMaxDepth: ${INTAKE_HARD_MAX_DEPTH}`);
|
|
2882
2880
|
if (cfg.parentDirs.length === 0) {
|
|
2883
|
-
println('
|
|
2881
|
+
println(' additionalDirs: (none — only inbox/ is watched)');
|
|
2884
2882
|
} else {
|
|
2885
|
-
println('
|
|
2883
|
+
println(' additionalDirs:');
|
|
2886
2884
|
for (const dir of cfg.parentDirs) println(` - ${dir}`);
|
|
2887
2885
|
}
|
|
2888
2886
|
println('');
|
|
@@ -2903,15 +2901,6 @@ async function cmdIntakeConfig(args) {
|
|
|
2903
2901
|
const depthFlag = tail.find((a) => a.startsWith('--depth='));
|
|
2904
2902
|
if (depthFlag) patch.maxDepth = Number(depthFlag.split('=')[1]);
|
|
2905
2903
|
|
|
2906
|
-
const projectFlag = tail.find((a) => a.startsWith('--include-project-inbox='));
|
|
2907
|
-
if (projectFlag) patch.includeProjectInbox = /^(on|true|1|yes)$/i.test(projectFlag.split('=')[1] || '');
|
|
2908
|
-
|
|
2909
|
-
const docsFlag = tail.find((a) => a.startsWith('--include-docs-intake='));
|
|
2910
|
-
if (docsFlag) patch.includeDocsIntake = /^(on|true|1|yes)$/i.test(docsFlag.split('=')[1] || '');
|
|
2911
|
-
|
|
2912
|
-
const rootFlag = tail.find((a) => a.startsWith('--include-root-inbox='));
|
|
2913
|
-
if (rootFlag) patch.includeRootInbox = /^(on|true|1|yes)$/i.test(rootFlag.split('=')[1] || '');
|
|
2914
|
-
|
|
2915
2904
|
const addFlags = tail.filter((a) => a.startsWith('--add-dir='));
|
|
2916
2905
|
const removeFlags = tail.filter((a) => a.startsWith('--remove-dir='));
|
|
2917
2906
|
|
|
@@ -2924,7 +2913,7 @@ async function cmdIntakeConfig(args) {
|
|
|
2924
2913
|
}
|
|
2925
2914
|
|
|
2926
2915
|
if (Object.keys(patch).length === 0) {
|
|
2927
|
-
errorln('Nothing to change. Pass --depth=N, --add-dir=PATH, --remove-dir=PATH
|
|
2916
|
+
errorln('Nothing to change. Pass --depth=N, --add-dir=PATH, or --remove-dir=PATH.');
|
|
2928
2917
|
process.exit(1);
|
|
2929
2918
|
}
|
|
2930
2919
|
|
|
@@ -3799,7 +3788,15 @@ async function cmdOrchestrate(args) {
|
|
|
3799
3788
|
}
|
|
3800
3789
|
|
|
3801
3790
|
async function cmdModels(args) {
|
|
3802
|
-
|
|
3791
|
+
const envPath = getUserEnvPath(HOME);
|
|
3792
|
+
const sub = args[0] && !args[0].startsWith('-') ? args[0] : null;
|
|
3793
|
+
const KNOWN_SUBCOMMANDS = new Set(['list', 'set', 'free', 'reset', 'resolve']);
|
|
3794
|
+
if (sub && !KNOWN_SUBCOMMANDS.has(sub)) {
|
|
3795
|
+
errorln(`Unknown models subcommand: ${sub}. Run \`construct models --help\` for the supported set.`);
|
|
3796
|
+
process.exit(1);
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3799
|
+
if (sub === 'resolve') {
|
|
3803
3800
|
const flag = (name) => { const i = args.indexOf(name); return i !== -1 ? args[i + 1] : undefined; };
|
|
3804
3801
|
const { resolveEmbeddedModel } = await import('../lib/embedded-contract/model-resolve.mjs');
|
|
3805
3802
|
const { wrapContractResult } = await import('../lib/embedded-contract/envelope.mjs');
|
|
@@ -3816,21 +3813,37 @@ async function cmdModels(args) {
|
|
|
3816
3813
|
println(JSON.stringify(envelope, null, 2));
|
|
3817
3814
|
return;
|
|
3818
3815
|
}
|
|
3816
|
+
// Legacy flag forms stay as compatibility aliases for the documented
|
|
3817
|
+
// subcommands. Each emits a one-line deprecation notice on stderr so stdout
|
|
3818
|
+
// stays clean while operators migrate to `models set|reset|free`.
|
|
3819
3819
|
const tier = args.find((arg) => arg.startsWith('--tier='))?.split('=')[1] ?? '';
|
|
3820
|
-
const
|
|
3821
|
-
const
|
|
3822
|
-
|
|
3820
|
+
const modelFlag = args.find((arg) => arg.startsWith('--model='))?.split('=')[1];
|
|
3821
|
+
const legacySetFlag = args.find((arg) => arg.startsWith('--set='))?.split('=')[1];
|
|
3822
|
+
const setModel = modelFlag ?? legacySetFlag ?? '';
|
|
3823
|
+
|
|
3824
|
+
if (sub === 'reset' || args.includes('--reset')) {
|
|
3825
|
+
if (sub !== 'reset') errorln('Note: `construct models --reset` is a deprecated alias for `construct models reset`.');
|
|
3823
3826
|
resetEnv(envPath);
|
|
3824
|
-
println(
|
|
3827
|
+
println(`Removed CX_MODEL_* overrides from ${envPath}.`);
|
|
3825
3828
|
await cmdSync([]);
|
|
3826
3829
|
return;
|
|
3827
3830
|
}
|
|
3828
|
-
if (tier && setModel) {
|
|
3831
|
+
if (sub === 'set' || (sub === null && tier && setModel)) {
|
|
3832
|
+
if (!tier || !setModel) {
|
|
3833
|
+
errorln('Usage: construct models set --tier=<reasoning|standard|fast> --model=<provider/model-id>');
|
|
3834
|
+
process.exit(1);
|
|
3835
|
+
}
|
|
3836
|
+
if (modelFlag === undefined && legacySetFlag !== undefined) {
|
|
3837
|
+
errorln('Note: `--set=<model>` is a deprecated alias for `--model=<model>`.');
|
|
3838
|
+
}
|
|
3839
|
+
if (sub === null) {
|
|
3840
|
+
errorln('Note: `construct models --tier=… --set=…` is a deprecated alias for `construct models set --tier=… --model=…`.');
|
|
3841
|
+
}
|
|
3829
3842
|
const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
|
|
3830
3843
|
const preferFree = args.includes('--prefer-free');
|
|
3831
3844
|
const preferFreeSameFamily = args.includes('--prefer-free-same-family');
|
|
3832
3845
|
const inferred = setModelWithTierInference(envPath, tier, setModel, registry.models ?? {}, { preferFree, preferFreeSameFamily });
|
|
3833
|
-
println(`Set ${tier} -> ${setModel} in
|
|
3846
|
+
println(`Set ${tier} -> ${setModel} in ${envPath}`);
|
|
3834
3847
|
if (preferFreeSameFamily) println('Prefer-free-same-family mode: enabled');
|
|
3835
3848
|
if (preferFree) println('Prefer-free mode: enabled');
|
|
3836
3849
|
println(`Resolved tier set:`);
|
|
@@ -3840,7 +3853,8 @@ async function cmdModels(args) {
|
|
|
3840
3853
|
await cmdSync([]);
|
|
3841
3854
|
return;
|
|
3842
3855
|
}
|
|
3843
|
-
if (args.includes('--poll')) {
|
|
3856
|
+
if (sub === 'free' || args.includes('--poll')) {
|
|
3857
|
+
if (sub !== 'free') errorln('Note: `construct models --poll` is a deprecated alias for `construct models free`.');
|
|
3844
3858
|
const apiKey = process.env.OPENROUTER_API_KEY || readOpenRouterApiKeyFromOpenCodeConfig();
|
|
3845
3859
|
if (!apiKey) {
|
|
3846
3860
|
errorln('Error: no OpenRouter API key found in OPENROUTER_API_KEY or OpenCode config.');
|
|
@@ -3867,7 +3881,7 @@ async function cmdModels(args) {
|
|
|
3867
3881
|
if (id) selections[currentTier] = id;
|
|
3868
3882
|
}
|
|
3869
3883
|
applyToEnv(envPath, selections);
|
|
3870
|
-
println(
|
|
3884
|
+
println(`Written to ${envPath}. Running construct sync...`);
|
|
3871
3885
|
await cmdSync([]);
|
|
3872
3886
|
return;
|
|
3873
3887
|
}
|
|
@@ -3904,12 +3918,12 @@ async function cmdModels(args) {
|
|
|
3904
3918
|
for (const [tier, model] of Object.entries(selections)) {
|
|
3905
3919
|
println(` ${tier.padEnd(11)} ${model}`);
|
|
3906
3920
|
}
|
|
3907
|
-
println(
|
|
3921
|
+
println(`Written to ${envPath}. Running construct sync...`);
|
|
3908
3922
|
setCheapestProviderPreference(envPath, true);
|
|
3909
3923
|
await cmdSync([]);
|
|
3910
3924
|
return;
|
|
3911
3925
|
}
|
|
3912
|
-
if (args.includes('--list') ||
|
|
3926
|
+
if (args.includes('--list') || sub === 'list') {
|
|
3913
3927
|
const { getProviderModelCatalog } = await import('../lib/model-router.mjs');
|
|
3914
3928
|
const { listChatModels } = await import('../apps/chat/engine/models.mjs');
|
|
3915
3929
|
const catalog = getProviderModelCatalog({ env: process.env, cwd: process.cwd() });
|
|
@@ -3939,7 +3953,7 @@ async function cmdModels(args) {
|
|
|
3939
3953
|
println('No tier has a model selected. Construct ships with no default.');
|
|
3940
3954
|
println('Pick one of:');
|
|
3941
3955
|
println(' • Dashboard → Models page (single source of truth)');
|
|
3942
|
-
println(' • construct models
|
|
3956
|
+
println(' • construct models set --tier=<reasoning|standard|fast> --model=<provider/model-id>');
|
|
3943
3957
|
println(' • construct models --apply Poll OpenRouter free catalog and seed all tiers');
|
|
3944
3958
|
}
|
|
3945
3959
|
}
|
|
@@ -4034,7 +4048,7 @@ async function cmdPluginEngine(args) {
|
|
|
4034
4048
|
const [action = 'list', ...rest] = args;
|
|
4035
4049
|
const isGlobal = rest.includes('--global');
|
|
4036
4050
|
const targetPath = isGlobal
|
|
4037
|
-
? path.join(HOME, '
|
|
4051
|
+
? path.join(configDir(HOME), 'plugins.json')
|
|
4038
4052
|
: path.join(process.cwd(), '.cx', 'plugins.json');
|
|
4039
4053
|
|
|
4040
4054
|
function readPluginsJson() {
|
|
@@ -4241,7 +4255,7 @@ async function cmdEvals(args) {
|
|
|
4241
4255
|
const report = await evaluateFixture(fixturePath);
|
|
4242
4256
|
|
|
4243
4257
|
try {
|
|
4244
|
-
const cacheDir = path.join(
|
|
4258
|
+
const cacheDir = path.join(doctorRoot(), 'evals');
|
|
4245
4259
|
fs.mkdirSync(cacheDir, { recursive: true });
|
|
4246
4260
|
const ts = new Date().toISOString();
|
|
4247
4261
|
fs.writeFileSync(
|
|
@@ -4438,7 +4452,7 @@ async function cmdDocsCheck(args) {
|
|
|
4438
4452
|
println('Commands with no linked how-to in docs/README.md:');
|
|
4439
4453
|
for (const name of result.uncovered) println(` ✗ construct ${name}`);
|
|
4440
4454
|
println('');
|
|
4441
|
-
println(`Add cookbook recipes under docs/cookbook/ and link them from the cookbook index.`);
|
|
4455
|
+
println(`Add cookbook recipes under docs/guides/cookbook/ and link them from the cookbook index.`);
|
|
4442
4456
|
process.exitCode = 1;
|
|
4443
4457
|
}
|
|
4444
4458
|
}
|
|
@@ -4449,7 +4463,7 @@ async function cmdDocsSite(args = []) {
|
|
|
4449
4463
|
if (check) {
|
|
4450
4464
|
const { drift } = checkFumadocsReferenceDrift({ rootDir: ROOT_DIR });
|
|
4451
4465
|
if (drift.length === 0) {
|
|
4452
|
-
ok('docs/reference/ is up to date.');
|
|
4466
|
+
ok('docs/guides/reference/ is up to date.');
|
|
4453
4467
|
return;
|
|
4454
4468
|
}
|
|
4455
4469
|
println('');
|
|
@@ -4460,9 +4474,9 @@ async function cmdDocsSite(args = []) {
|
|
|
4460
4474
|
}
|
|
4461
4475
|
const { written } = buildFumadocsReference({ rootDir: ROOT_DIR });
|
|
4462
4476
|
if (written.length === 0) {
|
|
4463
|
-
ok('docs/reference/ already up to date.');
|
|
4477
|
+
ok('docs/guides/reference/ already up to date.');
|
|
4464
4478
|
} else {
|
|
4465
|
-
ok(`docs/reference/ regenerated (${written.length} file${written.length === 1 ? '' : 's'}).`);
|
|
4479
|
+
ok(`docs/guides/reference/ regenerated (${written.length} file${written.length === 1 ? '' : 's'}).`);
|
|
4466
4480
|
}
|
|
4467
4481
|
}
|
|
4468
4482
|
|
|
@@ -4961,7 +4975,7 @@ async function cmdProvider(args) {
|
|
|
4961
4975
|
if (!id) { errorln(`Usage: construct provider plugins ${action} <id> [<package>]`); process.exit(1); }
|
|
4962
4976
|
const isGlobal = args.includes('--global');
|
|
4963
4977
|
const targetPath = isGlobal
|
|
4964
|
-
? path.join(HOME, '
|
|
4978
|
+
? path.join(configDir(HOME), 'providers.json')
|
|
4965
4979
|
: path.join(process.cwd(), '.cx', 'providers.json');
|
|
4966
4980
|
let data = { providers: [] };
|
|
4967
4981
|
try { data = JSON.parse(fs.readFileSync(targetPath, 'utf8')); } catch { /* fresh */ }
|
|
@@ -5486,7 +5500,7 @@ async function cmdTelemetryQuery(args) {
|
|
|
5486
5500
|
println(' errors [--agent=<name>] [--since=24h] error span summary');
|
|
5487
5501
|
println(' trace <traceId> full span tree');
|
|
5488
5502
|
println('');
|
|
5489
|
-
println('For Langfuse/Honeycomb/Grafana Tempo/Datadog, see docs/concepts/observability.md');
|
|
5503
|
+
println('For Langfuse/Honeycomb/Grafana Tempo/Datadog, see docs/guides/concepts/observability.md');
|
|
5490
5504
|
}
|
|
5491
5505
|
|
|
5492
5506
|
async function cmdHook(args) {
|
|
@@ -6553,7 +6567,7 @@ const handlers = new Map([
|
|
|
6553
6567
|
|
|
6554
6568
|
println('');
|
|
6555
6569
|
println(`Scaffolding a new profile draft. Three questions, then a preview.`);
|
|
6556
|
-
println(`Background: docs/concepts/persona-research.md and docs/concepts/profile-lifecycle.md`);
|
|
6570
|
+
println(`Background: docs/guides/concepts/persona-research.md and docs/guides/concepts/profile-lifecycle.md`);
|
|
6557
6571
|
println('');
|
|
6558
6572
|
if (!displayName) displayName = await ask('Display name?', id);
|
|
6559
6573
|
if (seedDepartments.length === 0) {
|
|
@@ -6612,7 +6626,7 @@ const handlers = new Map([
|
|
|
6612
6626
|
println('');
|
|
6613
6627
|
println(`✓ draft created at ${pathMod.relative(process.cwd(), result.dir)}`);
|
|
6614
6628
|
println('');
|
|
6615
|
-
println(`Next steps (in order, per docs/concepts/profile-lifecycle.md):`);
|
|
6629
|
+
println(`Next steps (in order, per docs/guides/concepts/profile-lifecycle.md):`);
|
|
6616
6630
|
println(` 1. Discover (cx-ux-researcher): fill personas/<role>.md from interviews + primary sources.`);
|
|
6617
6631
|
println(` 2. Frame (cx-product-manager): fill departments/<dept>.md charters and intake taxonomy.`);
|
|
6618
6632
|
println(` 3. Architect (cx-architect): reconcile role reuse vs new; populate departments[] in profile.json.`);
|
package/commands/plan/decide.md
CHANGED
|
@@ -11,4 +11,4 @@ ADR format:
|
|
|
11
11
|
- FILES AFFECTED: paths future contributors should inspect
|
|
12
12
|
- FOLLOW-UP: docs, tests, migrations, or risks to track
|
|
13
13
|
|
|
14
|
-
Save to `docs/adr/ADR-{NNN}-{slug}.md` and `.cx/decisions/` if those directories exist.
|
|
14
|
+
Save to `docs/decisions/adr/ADR-{NNN}-{slug}.md` and `.cx/decisions/` if those directories exist.
|
|
@@ -11,4 +11,4 @@ Structure:
|
|
|
11
11
|
- ROLLBACK: how to undo if something goes wrong
|
|
12
12
|
- ESCALATION: who to contact and when
|
|
13
13
|
|
|
14
|
-
Save to `docs/runbooks/{service}-{operation}.md` if the directory exists.
|
|
14
|
+
Save to `docs/operations/runbooks/{service}-{operation}.md` if the directory exists.
|
package/examples/README.md
CHANGED
|
@@ -78,7 +78,7 @@ Required internal coverage:
|
|
|
78
78
|
- Name the expected behavior in repo terms: branch confirmation, approval boundary, routing, verification, blocker surfacing, anti-pattern avoidance.
|
|
79
79
|
- For bad examples, show the failure plainly and explain why it violates the surface contract.
|
|
80
80
|
|
|
81
|
-
See `docs/concepts/prompt-surfaces.md` for the canonical public-vs-internal taxonomy.
|
|
81
|
+
See `docs/guides/concepts/prompt-surfaces.md` for the canonical public-vs-internal taxonomy.
|
|
82
82
|
|
|
83
83
|
## Scope
|
|
84
84
|
|
|
@@ -39,4 +39,4 @@ Edit sources, then re-run the generator. Outputs land in `.tmp/distribution-exam
|
|
|
39
39
|
node bin/construct export examples/distribution/sources/adr.md --to=pdf --figures
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
See [branding.md](../../docs/reference/branding.md) and [diagram-and-demo.md](../../docs/cookbook/diagram-and-demo.md).
|
|
42
|
+
See [branding.md](../../docs/guides/reference/branding.md) and [diagram-and-demo.md](../../docs/guides/cookbook/diagram-and-demo.md).
|
|
@@ -51,7 +51,7 @@ tokens -> dash
|
|
|
51
51
|
|
|
52
52
|
## Decision
|
|
53
53
|
|
|
54
|
-
Adopt `lib/brand-tokens.mjs` as the single source of truth for visual brand primitives. Document naming, voice, tone, and profile rebrand in `docs/reference/branding.md`. Enforce retired-font drift with `scripts/audit/03d-brand.mjs` in the audit ratchet.
|
|
54
|
+
Adopt `lib/brand-tokens.mjs` as the single source of truth for visual brand primitives. Document naming, voice, tone, and profile rebrand in `docs/guides/reference/branding.md`. Enforce retired-font drift with `scripts/audit/03d-brand.mjs` in the audit ratchet.
|
|
55
55
|
|
|
56
56
|
## Rationale
|
|
57
57
|
|
|
@@ -79,6 +79,6 @@ Two-way door for documentation and audit scope; one-way for shipped PDFs already
|
|
|
79
79
|
|
|
80
80
|
## References
|
|
81
81
|
|
|
82
|
-
- `docs/reference/branding.md`
|
|
82
|
+
- `docs/guides/reference/branding.md`
|
|
83
83
|
- `lib/brand-tokens.mjs`
|
|
84
84
|
- https://fonts.google.com/specimen/Space+Grotesk (accessed 2026-06-22)
|
|
@@ -60,6 +60,6 @@ One token set across all distributable formats:
|
|
|
60
60
|
|
|
61
61
|
- Export: `construct export … --to=pptx`
|
|
62
62
|
- Regenerate: `npm run examples:deck`
|
|
63
|
-
- Matrix: [Document I/O reference](/reference/document-io)
|
|
63
|
+
- Matrix: [Document I/O reference](/guides/reference/document-io)
|
|
64
64
|
|
|
65
65
|
Review outputs in `.tmp/distribution-examples/`.
|
|
@@ -157,5 +157,5 @@ https://langchain-ai.github.io/langgraph/ (accessed 2026-06-19).
|
|
|
157
157
|
|
|
158
158
|
- Construct artifact manifest: `specialists/artifact-manifest.json`
|
|
159
159
|
- PRD workflow skill: `skills/docs/prd-workflow.md`
|
|
160
|
-
- Publish cookbook: `docs/cookbook/diagram-and-demo.md`
|
|
160
|
+
- Publish cookbook: `docs/guides/cookbook/diagram-and-demo.md`
|
|
161
161
|
- Golden D2 sources: `tests/fixtures/publish/diagrams/`
|
|
@@ -32,7 +32,7 @@ Searched arXiv and vendor docs (2024–2026), reviewed three internal ADRs on in
|
|
|
32
32
|
|---|---|---|---|---|---|---|---|
|
|
33
33
|
| Multi-agent survey | secondary | B | 3 | 2023-08-01 | https://arxiv.org/abs/2308.08155 | yes | Patterns for specialist routing |
|
|
34
34
|
| LangGraph overview | primary | A | 4 | 2025-01-15 | https://langchain-ai.github.io/langgraph/ | yes | Stateful agent graphs |
|
|
35
|
-
| Construct architecture | primary | A | 5 | 2026-06-01 | docs/concepts/architecture.mdx | yes | Internal contract |
|
|
35
|
+
| Construct architecture | primary | A | 5 | 2026-06-01 | docs/guides/concepts/architecture.mdx | yes | Internal contract |
|
|
36
36
|
|
|
37
37
|
## Findings
|
|
38
38
|
|
|
@@ -77,4 +77,4 @@ Ship distribution examples (`npm run examples:distribution`) so evaluators can c
|
|
|
77
77
|
## References
|
|
78
78
|
|
|
79
79
|
- https://arxiv.org/abs/2308.08155 (accessed 2026-06-22)
|
|
80
|
-
- `docs/cookbook/diagram-and-demo.md`
|
|
80
|
+
- `docs/guides/cookbook/diagram-and-demo.md`
|
|
@@ -99,5 +99,5 @@ Preserve `construct tools detect --json` output and the failing pandoc/typst std
|
|
|
99
99
|
|
|
100
100
|
## References
|
|
101
101
|
|
|
102
|
-
- `docs/reference/document-io.md`
|
|
103
|
-
- `docs/cookbook/diagram-and-demo.md`
|
|
102
|
+
- `docs/guides/reference/document-io.md`
|
|
103
|
+
- `docs/guides/cookbook/diagram-and-demo.md`
|
|
@@ -26,7 +26,7 @@ Host-native agents optimize for single-session chat. Enterprise platform teams n
|
|
|
26
26
|
## Goals
|
|
27
27
|
|
|
28
28
|
1. **Prove value in five minutes** — `npm run examples:distribution` produces a browsable gallery with figures.
|
|
29
|
-
2. **One brand contract** — `docs/reference/branding.md` + `03d-brand` audit ratchet.
|
|
29
|
+
2. **One brand contract** — `docs/guides/reference/branding.md` + `03d-brand` audit ratchet.
|
|
30
30
|
3. **Profile-aware language** — intake queue labels follow active profile rebrand in CLI, session prelude, and dashboard API.
|
|
31
31
|
|
|
32
32
|
## Non-goals
|
|
@@ -71,7 +71,7 @@ export function buildConstructIntegrationBody({ hasBeadsBlock = false, variant =
|
|
|
71
71
|
'- **Durable state** lives in `.cx/` (context, knowledge, intake, traces) and Beads.',
|
|
72
72
|
' If services are down, resume from `plan.md`, `.cx/context.md`, the latest',
|
|
73
73
|
' `.cx/handoffs/` file, Beads, and git.',
|
|
74
|
-
'- **Signals**: drop a file into
|
|
74
|
+
'- **Signals**: drop a file into `inbox/`; `construct intake` classifies and routes it.',
|
|
75
75
|
'- **Specialists** (architect, reviewer, security, …) are dispatched by Construct — you',
|
|
76
76
|
' do not call them directly.',
|
|
77
77
|
tracker,
|
|
@@ -41,12 +41,16 @@ function inferRfcSubtype(rel, base) {
|
|
|
41
41
|
return 'rfc';
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// Match both this repo's bucketed layout (docs/specs/prd, docs/decisions/adr, …)
|
|
45
|
+
// and the init-lane layout that `construct init` scaffolds downstream (docs/prd,
|
|
46
|
+
// docs/adr, …), so artifact-type inference works in either project shape.
|
|
47
|
+
|
|
44
48
|
const PREFIX_RULES = [
|
|
45
|
-
[/^docs\/prd\//, inferPrdSubtype],
|
|
46
|
-
[/^docs\/adr\//, () => 'adr'],
|
|
47
|
-
[/^docs\/rfc\//, inferRfcSubtype],
|
|
48
|
-
[/^docs\/research\//, () => 'research-brief'],
|
|
49
|
-
[/^docs\/runbooks?\//, () => 'runbook'],
|
|
49
|
+
[/^docs\/(?:specs\/)?prd\//, inferPrdSubtype],
|
|
50
|
+
[/^docs\/(?:decisions\/)?adr\//, () => 'adr'],
|
|
51
|
+
[/^docs\/(?:decisions\/)?rfc\//, inferRfcSubtype],
|
|
52
|
+
[/^docs\/(?:notes\/)?research\//, () => 'research-brief'],
|
|
53
|
+
[/^docs\/(?:operations\/)?runbooks?\//, () => 'runbook'],
|
|
50
54
|
[/^\.cx\/research\//, () => 'research-brief'],
|
|
51
55
|
];
|
|
52
56
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Merges live registry, contracts, skills, prompts, and audit-enrichments into
|
|
5
5
|
* one machine-readable matrix. Powers `construct audit specialists` and the
|
|
6
|
-
* human-readable docs/concepts/specialist-skill-audit.md companion.
|
|
6
|
+
* human-readable docs/guides/concepts/specialist-skill-audit.md companion.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import fs from 'node:fs';
|
package/lib/audit-trail.mjs
CHANGED
|
@@ -16,17 +16,17 @@
|
|
|
16
16
|
* Corrupted links cannot be re-derived, so a `chain_reset` boundary line seals
|
|
17
17
|
* the damaged prefix as immutable legacy and re-bases the live chain;
|
|
18
18
|
* verifyChain validates only from the last boundary forward. See
|
|
19
|
-
* docs/concepts/observability.md.
|
|
19
|
+
* docs/guides/concepts/observability.md.
|
|
20
20
|
*/
|
|
21
21
|
import { readFileSync, existsSync, statSync, mkdirSync } from 'node:fs';
|
|
22
22
|
import { createHash } from 'node:crypto';
|
|
23
23
|
import { join, dirname } from 'node:path';
|
|
24
|
-
import { homedir } from 'node:os';
|
|
25
24
|
|
|
26
25
|
import { appendBounded, readLastLineAcrossSegments } from './logging/rotate.mjs';
|
|
27
26
|
import { withFileLockSync } from './storage/file-lock.mjs';
|
|
27
|
+
import { doctorRoot } from './config/xdg.mjs';
|
|
28
28
|
|
|
29
|
-
const AUDIT_FILE = join(
|
|
29
|
+
const AUDIT_FILE = join(doctorRoot(), 'audit-trail.jsonl');
|
|
30
30
|
|
|
31
31
|
function sha256(input) {
|
|
32
32
|
return createHash('sha256').update(input).digest('hex');
|