@geraldmaron/construct 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/bin/construct +206 -62
- package/commands/plan/decide.md +1 -1
- package/commands/remember/runbook.md +1 -1
- package/examples/README.md +1 -1
- package/examples/distribution/README.md +1 -1
- package/examples/distribution/sources/adr.md +2 -2
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/examples/distribution/sources/prd-platform.md +1 -1
- package/examples/distribution/sources/research-brief.md +2 -2
- package/examples/distribution/sources/rfc-platform.md +1 -1
- package/examples/distribution/sources/runbook.md +2 -2
- package/examples/distribution/sources/strategy.md +1 -1
- package/lib/agent-instructions/inject.mjs +1 -1
- package/lib/artifact-manifest.mjs +150 -4
- package/lib/artifact-type-from-path.mjs +9 -5
- package/lib/artifact-workflow.mjs +252 -0
- package/lib/audit-specialists.mjs +1 -1
- package/lib/audit-trail.mjs +3 -3
- package/lib/auto-docs.mjs +13 -11
- package/lib/bootstrap/built-ins.mjs +3 -3
- package/lib/boundary.mjs +3 -2
- package/lib/brand-prose.mjs +1 -1
- package/lib/cache-governor.js +4 -4
- package/lib/certification/document-io-fixtures.mjs +1 -1
- package/lib/chat/config.mjs +2 -2
- package/lib/chat/desktop-binary.mjs +5 -3
- package/lib/chat/present.mjs +1 -0
- package/lib/cli-commands.mjs +23 -6
- package/lib/cli-service-inventory.mjs +42 -0
- package/lib/comment-lint.mjs +9 -5
- package/lib/config/alias.mjs +3 -2
- package/lib/config/intake-policy.mjs +139 -0
- package/lib/config/project-config.mjs +106 -7
- package/lib/config/schema.mjs +45 -6
- package/lib/config/source-targets.mjs +311 -0
- package/lib/config/xdg.mjs +63 -0
- package/lib/contracts/violation-log.mjs +2 -2
- package/lib/cost-ledger.mjs +4 -3
- package/lib/cost.mjs +3 -2
- package/lib/decisions/registry.mjs +3 -3
- package/lib/distill.mjs +2 -2
- package/lib/docs-verify.mjs +2 -0
- package/lib/doctor/audit.mjs +4 -3
- package/lib/doctor/cli.mjs +0 -1
- package/lib/doctor/index.mjs +4 -4
- package/lib/doctor/report.mjs +3 -3
- package/lib/doctor/source-checkout.mjs +16 -0
- package/lib/doctor/watchers/bd-watch.mjs +5 -5
- package/lib/doctor/watchers/cost.mjs +4 -4
- package/lib/doctor/watchers/disk.mjs +4 -3
- package/lib/doctor/watchers/service-health.mjs +5 -5
- package/lib/document-export.mjs +39 -17
- package/lib/document-extract.mjs +3 -3
- package/lib/document-ingest.mjs +13 -3
- package/lib/efficiency.mjs +2 -1
- package/lib/embed/approval-queue.mjs +3 -2
- package/lib/embed/auto-sources.mjs +44 -0
- package/lib/embed/cli.mjs +9 -6
- package/lib/embed/config.mjs +6 -5
- package/lib/embed/daemon.mjs +6 -5
- package/lib/embed/demand-fetch.mjs +50 -19
- package/lib/embed/inbox-live-watcher.mjs +1 -1
- package/lib/embed/inbox.mjs +52 -41
- package/lib/embed/intake-metrics.mjs +3 -2
- package/lib/embed/reconcile.mjs +1 -1
- package/lib/embed/semantic.mjs +3 -2
- package/lib/embed/supervision.mjs +4 -2
- package/lib/embed/worker.mjs +2 -1
- package/lib/embedded-contract/index.mjs +11 -0
- package/lib/engine/registry.mjs +3 -3
- package/lib/env-config.mjs +34 -12
- package/lib/export-branding.mjs +32 -0
- package/lib/features.mjs +4 -3
- package/lib/health-check.mjs +7 -6
- package/lib/hook-health.mjs +5 -4
- package/lib/hooks/_lib/log.mjs +4 -3
- package/lib/hooks/_lib/output-mode.mjs +2 -1
- package/lib/hooks/agent-tracker.mjs +3 -4
- package/lib/hooks/audit-reads.mjs +2 -2
- package/lib/hooks/audit-trail.mjs +2 -2
- package/lib/hooks/bash-output-logger.mjs +3 -3
- package/lib/hooks/brand-prose-lint.mjs +1 -1
- package/lib/hooks/ci-status-check.mjs +3 -2
- package/lib/hooks/context-watch.mjs +2 -2
- package/lib/hooks/context-window-recovery.mjs +5 -5
- package/lib/hooks/dep-audit.mjs +2 -2
- package/lib/hooks/doc-coupling-check.mjs +3 -2
- package/lib/hooks/edit-accumulator.mjs +3 -2
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/guard-bash.mjs +2 -2
- package/lib/hooks/model-fallback.mjs +2 -1
- package/lib/hooks/policy-engine.mjs +4 -3
- package/lib/hooks/pre-compact.mjs +7 -7
- package/lib/hooks/readme-age-check.mjs +2 -2
- package/lib/hooks/session-optimize.mjs +4 -3
- package/lib/hooks/session-start.mjs +20 -16
- package/lib/hooks/stop-notify.mjs +9 -8
- package/lib/hooks/stop-typecheck.mjs +4 -3
- package/lib/hooks/test-watch.mjs +2 -2
- package/lib/host-disposition.mjs +1 -1
- package/lib/ingest/provider-extract.mjs +2 -1
- package/lib/init/detect-existing-structure.mjs +2 -2
- package/lib/init/doc-lanes.mjs +3 -10
- package/lib/init-docs.mjs +12 -54
- package/lib/init-unified.mjs +47 -92
- package/lib/init.mjs +7 -5
- package/lib/install/desktop-binary-download.mjs +5 -2
- package/lib/intake/constants.mjs +29 -0
- package/lib/intake/daemon.mjs +31 -8
- package/lib/intake/intake-config.mjs +32 -112
- package/lib/integrations/intake-integrations.mjs +8 -7
- package/lib/knowledge/rag.mjs +2 -2
- package/lib/knowledge/search.mjs +15 -15
- package/lib/maintenance/cleanup.mjs +14 -10
- package/lib/mcp/server.mjs +20 -2
- package/lib/mcp/tools/embedded-contract.mjs +14 -0
- package/lib/mcp/tools/project.mjs +2 -1
- package/lib/model-cheapest-provider.mjs +4 -3
- package/lib/model-pricing.mjs +3 -2
- package/lib/model-router.mjs +4 -3
- package/lib/models/catalog.mjs +2 -1
- package/lib/models/execution-capability-profile.mjs +2 -1
- package/lib/models/provider-poll.mjs +2 -1
- package/lib/ollama-manager.mjs +0 -1
- package/lib/op-log.mjs +2 -1
- package/lib/opencode-runtime-plugin.mjs +3 -2
- package/lib/oracle/artifact-gate.mjs +3 -0
- package/lib/oracle/cli.mjs +2 -1
- package/lib/oracle/execute.mjs +3 -2
- package/lib/oracle/index.mjs +2 -1
- package/lib/oracle/read-model.mjs +3 -2
- package/lib/performance/generate.mjs +4 -3
- package/lib/platforms/capabilities.mjs +1 -1
- package/lib/playwright-demo.mjs +2 -0
- package/lib/plugin-registry.mjs +2 -1
- package/lib/profiles/lifecycle.mjs +3 -3
- package/lib/project-profile.mjs +2 -1
- package/lib/project-root.mjs +9 -7
- package/lib/provider-capabilities.js +4 -3
- package/lib/providers/auth-manager.mjs +2 -1
- package/lib/providers/copilot-auth.mjs +3 -2
- package/lib/providers/creds.mjs +3 -2
- package/lib/providers/registry.mjs +3 -3
- package/lib/providers/secret-resolver.mjs +3 -2
- package/lib/read-tracker-store.mjs +2 -1
- package/lib/reconcile/mcp-entry-reconcile.mjs +2 -2
- package/lib/reflect.mjs +2 -1
- package/lib/registry/generate-docs.mjs +2 -2
- package/lib/roles/approval-surface.mjs +5 -4
- package/lib/roles/event-bus.mjs +0 -1
- package/lib/roles/gateway.mjs +6 -2
- package/lib/rules-delivery.mjs +1 -1
- package/lib/runtime-pressure.mjs +5 -3
- package/lib/sandbox.mjs +3 -2
- package/lib/scheduler/solo.mjs +6 -4
- package/lib/server/auth.mjs +4 -3
- package/lib/server/index.mjs +53 -26
- package/lib/server/insights.mjs +5 -4
- package/lib/server/rate-limit.mjs +5 -3
- package/lib/server/webhook.mjs +5 -3
- package/lib/service-manager.mjs +5 -3
- package/lib/setup.mjs +9 -8
- package/lib/status.mjs +6 -5
- package/lib/storage/embeddings-local.mjs +3 -2
- package/lib/storage/sync.mjs +2 -2
- package/lib/telemetry/client.mjs +0 -1
- package/lib/telemetry/intent-verifications.mjs +6 -6
- package/lib/telemetry/model-pricing-catalog.mjs +4 -3
- package/lib/telemetry/rule-calls.mjs +3 -3
- package/lib/telemetry/skill-calls.mjs +3 -3
- package/lib/template-registry.mjs +1 -0
- package/lib/templates/visual-requirements.mjs +1 -1
- package/lib/test-corpus-inventory.mjs +1 -1
- package/lib/uninstall/uninstall.mjs +12 -10
- package/package.json +3 -3
- package/platforms/claude/settings.template.json +43 -43
- package/rules/common/no-fabrication.md +1 -1
- package/rules/common/release-gates.md +1 -1
- package/rules/common/research.md +1 -1
- package/rules/common/review-before-change.md +1 -1
- package/scripts/sync-specialists.mjs +11 -8
- package/skills/docs/adr-workflow.md +3 -3
- package/skills/docs/init-docs.md +9 -9
- package/skills/docs/prd-workflow.md +5 -5
- package/skills/docs/product-intelligence-review.md +1 -1
- package/skills/docs/product-intelligence-workflow.md +2 -2
- package/skills/docs/product-signal-workflow.md +1 -1
- package/skills/docs/runbook-workflow.md +4 -4
- package/skills/exploration/unknown-codebase-onboarding.md +1 -1
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/routing.md +3 -3
- package/specialists/artifact-manifest.json +11 -2
- package/specialists/artifact-manifest.schema.json +31 -1
- package/specialists/prompts/cx-architect.md +1 -1
- package/specialists/prompts/cx-docs-keeper.md +1 -1
- package/specialists/prompts/cx-researcher.md +1 -1
- package/specialists/prompts/cx-sre.md +1 -1
- package/specialists/role-manifests.json +6 -6
- package/templates/distribution/construct-reference.docx +0 -0
- package/templates/docs/README.md +125 -0
- package/templates/docs/construct_guide.md +3 -4
- package/templates/docs/persona-artifact.md +1 -1
- package/templates/docs/prds/README.md +25 -0
- package/templates/docs/prds/templates/_template.md +206 -0
- package/templates/docs/prds/templates/meta-prd.template.md +177 -0
- package/templates/docs/prds/templates/prd-business.template.md +61 -0
- package/templates/docs/prds/templates/prd-platform.template.md +81 -0
- package/templates/docs/prds/templates/prfaq.template.md +38 -0
- package/templates/docs/rfcs/README.md +22 -0
- package/templates/docs/rfcs/templates/_template.md +58 -0
- package/templates/docs/rfcs/templates/rfc-platform.template.md +72 -0
- package/templates/homebrew/construct.rb +1 -1
- package/templates/workflows/new-feature.yml +8 -8
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Bootstrap local services (once per machine, opt-in to machine-scope writes):
|
|
|
28
28
|
construct install --scope=user --yes
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
`construct install` defaults to `--scope=project`, which writes nothing and prints scope guidance — see the [footprint contract](#footprint-contract) below or [ADR 0029](docs/adr/0029-install-scopes-and-hook-budgets.md). Use `--scope=user` for machine setup, `--scope=both` for both.
|
|
31
|
+
`construct install` defaults to `--scope=project`, which writes nothing and prints scope guidance — see the [footprint contract](#footprint-contract) below or [ADR 0029](docs/decisions/adr/0029-install-scopes-and-hook-budgets.md). Use `--scope=user` for machine setup, `--scope=both` for both.
|
|
32
32
|
|
|
33
33
|
Initialize a project:
|
|
34
34
|
|
|
@@ -87,7 +87,7 @@ Pick or change modes with `construct config mode [solo|team|enterprise]`. [Deplo
|
|
|
87
87
|
|
|
88
88
|
## Intake
|
|
89
89
|
|
|
90
|
-
Anything dropped into
|
|
90
|
+
Anything dropped into `inbox/` (a bug report, a customer comment, a competitor PDF, a postmortem draft) is classified by the active profile's intake taxonomy. The default `rnd` profile uses bug, user-signal, experiment, architecture, incident, security, requirement, research, ops, eval-finding, launch-asset, legal-compliance. The `operations` profile uses request, incident, ops, security, docs. The `creative` profile uses brief, content-request, asset, experiment, report. The `research` profile uses question, study, synthesis, report.
|
|
91
91
|
|
|
92
92
|
Each signal gets a primary owner and a recommended handoff chain. Inspect with `construct intake list` and `construct intake show <id>`. Generate a task graph with `construct graph from-intake <id>`. The classifier runs in the daemon and is deterministic. The agent in your editor does the actual analysis. [Intake and triage](https://geraldmaron.github.io/construct/concepts/intake-and-triage).
|
|
93
93
|
|
|
@@ -111,11 +111,11 @@ Construct's writes are scoped and disclosed up front. The default `construct ins
|
|
|
111
111
|
| Machine | `construct install --scope=user` | `~/.construct/config.env`, `~/.construct/lib` (symlink), `~/.construct/services/`, `~/Library/LaunchAgents/` (macOS), MCP entries in `~/.config/opencode/opencode.json` and `~/.codex/config.toml`, marker block in `~/.claude/CLAUDE.md`, hook injection in `~/.claude/settings.json` (last two require interactive consent or `--yes`) |
|
|
112
112
|
| Never touched | — | Shell rc files (`~/.bashrc`, `~/.zshrc`), npm global config, `git config --global` |
|
|
113
113
|
|
|
114
|
-
Full table with file:line citations and the per-hook performance budget contract: [Architecture — Footprint contract](https://geraldmaron.github.io/construct/concepts/architecture#footprint-contract) and [ADR 0029](docs/adr/0029-install-scopes-and-hook-budgets.md).
|
|
114
|
+
Full table with file:line citations and the per-hook performance budget contract: [Architecture — Footprint contract](https://geraldmaron.github.io/construct/concepts/architecture#footprint-contract) and [ADR 0029](docs/decisions/adr/0029-install-scopes-and-hook-budgets.md).
|
|
115
115
|
|
|
116
116
|
## Learning loops
|
|
117
117
|
|
|
118
|
-
Construct gets smarter on its own. Every session ends with an automatic capture: tools used, files touched, what the final reply said. That goes into `.cx/observations/` and is searchable from the next session. See [`docs/concepts/learning-loops.mdx`](./docs/concepts/learning-loops.mdx) for what's wired, what's coming, and how to turn pieces off.
|
|
118
|
+
Construct gets smarter on its own. Every session ends with an automatic capture: tools used, files touched, what the final reply said. That goes into `.cx/observations/` and is searchable from the next session. See [`docs/guides/concepts/learning-loops.mdx`](./docs/guides/concepts/learning-loops.mdx) for what's wired, what's coming, and how to turn pieces off.
|
|
119
119
|
|
|
120
120
|
## `.cx/` is local-only runtime state
|
|
121
121
|
|
|
@@ -148,7 +148,7 @@ The embed daemon writes its supervisor stdout log to `~/.cx/runtime/embed-daemon
|
|
|
148
148
|
|
|
149
149
|
| Command | What it does |
|
|
150
150
|
|---|---|
|
|
151
|
-
| `construct artifact` |
|
|
151
|
+
| `construct artifact` | Plan or locally execute manifest-backed artifact workflows with execution provenance |
|
|
152
152
|
| `construct ask` | One-shot ask against the active knowledge index |
|
|
153
153
|
| `construct bootstrap` | Import seed observation corpus into local memory store for cold-start acceleration |
|
|
154
154
|
| `construct customer` | Manage customer profiles for product intelligence |
|
|
@@ -226,7 +226,7 @@ The embed daemon writes its supervisor stdout log to `~/.cx/runtime/embed-daemon
|
|
|
226
226
|
| `construct doc` | Verify or inspect auditability stamps on Construct-generated markdown files |
|
|
227
227
|
| `construct docs:check` | Check for missing how-to guides (alias for `docs check`) |
|
|
228
228
|
| `construct docs:reconcile` | Reconcile docs against the registry |
|
|
229
|
-
| `construct docs:site` | Regenerate generated reference pages under docs/reference/ |
|
|
229
|
+
| `construct docs:site` | Regenerate generated reference pages under docs/guides/reference/ |
|
|
230
230
|
| `construct docs:update` | Regenerate AUTO-managed doc regions (alias for `docs update`) |
|
|
231
231
|
| `construct docs:verify` | Validate documentation quality (alias for `docs verify`) |
|
|
232
232
|
| `construct impact` | Change-impact analysis — map changed files to affected tests, capabilities, and workflows |
|
|
@@ -257,6 +257,7 @@ The embed daemon writes its supervisor stdout log to `~/.cx/runtime/embed-daemon
|
|
|
257
257
|
| `construct roles:set` | Activate a role contract |
|
|
258
258
|
| `construct scheduler` | Manage scheduled background jobs (tag-mining, doc-hygiene, skill-rollup) |
|
|
259
259
|
| `construct skills` | Skill relevance detection |
|
|
260
|
+
| `construct sources` | Manage typed integration source targets in construct.config.json |
|
|
260
261
|
| `construct uninstall` | Remove Construct state |
|
|
261
262
|
| `construct update` | Reinstall this checkout |
|
|
262
263
|
| `construct upgrade` | Upgrade to latest npm version |
|
|
@@ -268,7 +269,7 @@ The embed daemon writes its supervisor stdout log to `~/.cx/runtime/embed-daemon
|
|
|
268
269
|
|
|
269
270
|
- [`CONTRIBUTING.md`](./CONTRIBUTING.md). Branch workflow, gates, review expectations.
|
|
270
271
|
- [`CHANGELOG.md`](./CHANGELOG.md). Release history.
|
|
271
|
-
- [`docs/concepts/architecture.mdx`](./docs/concepts/architecture.mdx). Canonical architecture.
|
|
272
|
+
- [`docs/guides/concepts/architecture.mdx`](./docs/guides/concepts/architecture.mdx). Canonical architecture.
|
|
272
273
|
- [`AGENTS.md`](./AGENTS.md). Agent operating contract.
|
|
273
274
|
|
|
274
275
|
## Project structure
|
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');
|
|
@@ -500,15 +501,19 @@ async function cmdDoctor() {
|
|
|
500
501
|
|
|
501
502
|
const checks = [];
|
|
502
503
|
const add = (label, pass, optional = false) => checks.push({ label, pass, optional });
|
|
504
|
+
const { isConstructSourceCheckout } = await import('../lib/doctor/source-checkout.mjs');
|
|
505
|
+
const isSourceCheckout = isConstructSourceCheckout(ROOT_DIR);
|
|
503
506
|
add('registry.json exists', fs.existsSync(path.join(ROOT_DIR, 'specialists', 'registry.json')));
|
|
504
507
|
add('sync-specialists.mjs exists', fs.existsSync(path.join(ROOT_DIR, 'scripts', 'sync-specialists.mjs')));
|
|
505
508
|
|
|
506
|
-
// Dashboard static build output
|
|
507
|
-
// emits a static export into lib/server/static/ via its postbuild script;
|
|
508
|
-
// an empty static dir means lib/server/index.mjs will serve 404s.
|
|
509
|
+
// Dashboard static build output is absent from published npm installs.
|
|
509
510
|
|
|
510
|
-
|
|
511
|
-
|
|
511
|
+
if (isSourceCheckout) {
|
|
512
|
+
const dashboardIndex = path.join(ROOT_DIR, 'lib', 'server', 'static', 'index.html');
|
|
513
|
+
add('Dashboard built (lib/server/static/index.html)', fs.existsSync(dashboardIndex));
|
|
514
|
+
} else {
|
|
515
|
+
add('Dashboard built (skipped — published package)', true, true);
|
|
516
|
+
}
|
|
512
517
|
|
|
513
518
|
// Project .gitignore must cover .cx/. Auto-fix via --fix-gitignore.
|
|
514
519
|
// Only checks when cwd looks like a Construct project (has .cx/ or
|
|
@@ -588,7 +593,7 @@ async function cmdDoctor() {
|
|
|
588
593
|
// minute at 50MB, but a freshly-upgraded install with a 34GB legacy
|
|
589
594
|
// log shouldn't wait 60s. --fix-embed-log forces rotation now.
|
|
590
595
|
|
|
591
|
-
const embedLogPath = path.join(userHome, '
|
|
596
|
+
const embedLogPath = path.join(doctorRoot(userHome), 'runtime', 'embed-daemon.log');
|
|
592
597
|
if (fs.existsSync(embedLogPath)) {
|
|
593
598
|
const sizeMb = fs.statSync(embedLogPath).size / 1024 / 1024;
|
|
594
599
|
const FORCE_THRESHOLD_MB = Number(process.env.CONSTRUCT_DOCTOR_EMBED_LOG_FORCE_MB) || 500;
|
|
@@ -623,11 +628,11 @@ async function cmdDoctor() {
|
|
|
623
628
|
'intent-verifications.jsonl',
|
|
624
629
|
];
|
|
625
630
|
const legacyState = projectScopedNames
|
|
626
|
-
.map((name) => ({ name, path: path.join(userHome,
|
|
631
|
+
.map((name) => ({ name, path: path.join(doctorRoot(userHome), name) }))
|
|
627
632
|
.filter((p) => fs.existsSync(p.path) && fs.statSync(p.path).size > 0);
|
|
628
633
|
|
|
629
634
|
if (legacyState.length > 0 && wantsFixMigrateState) {
|
|
630
|
-
const archiveDir = path.join(userHome, '
|
|
635
|
+
const archiveDir = path.join(doctorRoot(userHome), 'legacy');
|
|
631
636
|
fs.mkdirSync(archiveDir, { recursive: true });
|
|
632
637
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
633
638
|
const moved = [];
|
|
@@ -735,13 +740,13 @@ async function cmdDoctor() {
|
|
|
735
740
|
const vscodeSettingsOk = vscodeSettingsPaths.some((candidate) => fs.existsSync(candidate));
|
|
736
741
|
if (vscodeSettingsOk) add('VS Code settings file', true, true);
|
|
737
742
|
}
|
|
738
|
-
add('User config ready', fs.existsSync(getUserEnvPath(HOME)) || fs.existsSync(
|
|
743
|
+
add('User config ready', fs.existsSync(getUserEnvPath(HOME)) || fs.existsSync(configDir(HOME)), true);
|
|
739
744
|
add('skills/ directory', fs.existsSync(path.join(ROOT_DIR, 'skills')));
|
|
740
745
|
add('rules/common/', fs.existsSync(path.join(ROOT_DIR, 'rules', 'common')));
|
|
741
746
|
add('specialists/prompts/ directory', fs.existsSync(path.join(ROOT_DIR, 'specialists', 'prompts')));
|
|
742
747
|
add('Hybrid storage env keys documented', fs.existsSync(path.join(ROOT_DIR, '.env.example')));
|
|
743
|
-
fs.mkdirSync(path.join(
|
|
744
|
-
add('
|
|
748
|
+
fs.mkdirSync(path.join(doctorRoot(), 'performance-reviews'), { recursive: true });
|
|
749
|
+
add('performance-reviews/ ready', true);
|
|
745
750
|
|
|
746
751
|
// construct.config.json — optional today (config is permitted to be absent;
|
|
747
752
|
// env vars + defaults still work). The check enforces that when the file
|
|
@@ -761,33 +766,29 @@ async function cmdDoctor() {
|
|
|
761
766
|
|
|
762
767
|
// Claude Code Stop-hook installation check. The settings template
|
|
763
768
|
// wires several Stop hooks (policy-engine / stop-typecheck /
|
|
764
|
-
// stop-notify / readme-age-check)
|
|
765
|
-
//
|
|
766
|
-
//
|
|
767
|
-
//
|
|
768
|
-
//
|
|
769
|
-
// command away.
|
|
769
|
+
// stop-notify / readme-age-check) whose commands sync resolves to the
|
|
770
|
+
// hook lib symlink. If those scripts don't resolve, every Stop hook
|
|
771
|
+
// fails silently — including stop-notify, which is what appends session
|
|
772
|
+
// cost data. Result: the dashboard stops updating and the user sees stale
|
|
773
|
+
// data. Surface loudly so the fix is one command away.
|
|
770
774
|
try {
|
|
771
775
|
const settingsPath = path.join(HOME, '.claude', 'settings.json');
|
|
772
776
|
if (fs.existsSync(settingsPath)) {
|
|
773
777
|
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
774
778
|
const stopHooks = settings?.hooks?.Stop || [];
|
|
775
|
-
const installRoot = path.join(HOME, '
|
|
776
|
-
const installed = fs.existsSync(installRoot);
|
|
779
|
+
const installRoot = path.join(configDir(HOME), 'lib', 'hooks');
|
|
777
780
|
const referenced = [];
|
|
778
781
|
for (const group of stopHooks) {
|
|
779
782
|
for (const h of (group?.hooks || [])) {
|
|
780
783
|
const cmd = String(h?.command || '');
|
|
781
|
-
const m = cmd.match(
|
|
784
|
+
const m = cmd.match(/(?:^|["\s])(\/[^"\s]*\/lib\/hooks\/[a-zA-Z0-9_-]+\.mjs)/);
|
|
782
785
|
if (m) referenced.push(m[1]);
|
|
783
786
|
}
|
|
784
787
|
}
|
|
785
|
-
const missing =
|
|
786
|
-
? referenced.filter((f) => !fs.existsSync(path.join(installRoot, f)))
|
|
787
|
-
: referenced;
|
|
788
|
+
const missing = referenced.filter((scriptPath) => !fs.existsSync(scriptPath));
|
|
788
789
|
if (referenced.length > 0 && missing.length > 0) {
|
|
789
790
|
add(
|
|
790
|
-
`Claude Stop hooks broken: ${missing.length} hook${missing.length === 1 ? '' : 's'}
|
|
791
|
+
`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$/, '')}).`,
|
|
791
792
|
false,
|
|
792
793
|
false,
|
|
793
794
|
);
|
|
@@ -858,8 +859,21 @@ async function cmdDoctor() {
|
|
|
858
859
|
add('Headhunt classifier agents exist in registry', classifierOrphans.length === 0);
|
|
859
860
|
const skillAudit = auditSkills({ rootDir: ROOT_DIR, silent: true });
|
|
860
861
|
add('No declared skills missing on disk', skillAudit.pass, false);
|
|
861
|
-
|
|
862
|
-
|
|
862
|
+
if (isSourceCheckout) {
|
|
863
|
+
const specialistAudit = auditSpecialists({ rootDir: ROOT_DIR, silent: true });
|
|
864
|
+
add('Specialist/skill audit cross-checks', specialistAudit.pass, false);
|
|
865
|
+
|
|
866
|
+
// The catalog is the public-service source of truth; doctor joins it to runtime dispatch
|
|
867
|
+
// and the generated reference so a command that is unroutable or undocumented surfaces here.
|
|
868
|
+
|
|
869
|
+
const { buildCliServiceInventory } = await import('../lib/cli-service-inventory.mjs');
|
|
870
|
+
const cliDrift = buildCliServiceInventory({ rootDir: ROOT_DIR })
|
|
871
|
+
.filter((entry) => !entry.runnable || !entry.documented);
|
|
872
|
+
add('Public CLI dispatch/reference parity', cliDrift.length === 0, false);
|
|
873
|
+
} else {
|
|
874
|
+
add('Specialist/skill audit cross-checks (skipped — published package)', true, true);
|
|
875
|
+
add('Public CLI dispatch/reference parity (skipped — published package)', true, true);
|
|
876
|
+
}
|
|
863
877
|
const { lintResearchRepo } = await import('../lib/research-lint.mjs');
|
|
864
878
|
const researchLintResults = lintResearchRepo({ rootDir: process.cwd() });
|
|
865
879
|
add('Research artifacts meet minimum evidence structure', researchLintResults.every((entry) => entry.errors.length === 0), true);
|
|
@@ -1056,7 +1070,7 @@ async function cmdDoctor() {
|
|
|
1056
1070
|
}
|
|
1057
1071
|
|
|
1058
1072
|
try {
|
|
1059
|
-
const failuresPath = path.join(
|
|
1073
|
+
const failuresPath = path.join(doctorRoot(), 'hook-failures.jsonl');
|
|
1060
1074
|
if (fs.existsSync(failuresPath)) {
|
|
1061
1075
|
const lines = fs.readFileSync(failuresPath, 'utf8').trim().split('\n').filter(Boolean);
|
|
1062
1076
|
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
|
@@ -1502,7 +1516,7 @@ async function cmdMigrate(args) {
|
|
|
1502
1516
|
}
|
|
1503
1517
|
|
|
1504
1518
|
async function cmdReview(args) {
|
|
1505
|
-
fs.mkdirSync(path.join(
|
|
1519
|
+
fs.mkdirSync(path.join(doctorRoot(), 'performance-reviews'), { recursive: true });
|
|
1506
1520
|
const sub = args[0];
|
|
1507
1521
|
|
|
1508
1522
|
if (!sub || sub === 'run') {
|
|
@@ -1561,17 +1575,18 @@ async function cmdExport(args) {
|
|
|
1561
1575
|
const outFlag = args.find((a) => a.startsWith('--output='))?.split('=')[1];
|
|
1562
1576
|
const detectOnly = args.includes('--detect');
|
|
1563
1577
|
const figures = !args.includes('--no-figures');
|
|
1578
|
+
const branding = args.includes('--plain') || args.includes('--no-brand') ? 'plain' : 'construct';
|
|
1564
1579
|
|
|
1565
1580
|
const { detect, exportMarkdown, EXPORT_FORMATS } = await import('../lib/document-export.mjs');
|
|
1566
1581
|
|
|
1567
1582
|
if (detectOnly) {
|
|
1568
1583
|
const format = toFlag || 'pdf';
|
|
1569
|
-
println(JSON.stringify(detect(format), null, 2));
|
|
1584
|
+
println(JSON.stringify(detect(format, process.env, { branding }), null, 2));
|
|
1570
1585
|
return;
|
|
1571
1586
|
}
|
|
1572
1587
|
|
|
1573
1588
|
if (!inputPath) {
|
|
1574
|
-
errorln('Usage: construct export <markdown-file> --to=<pdf|docx|html> [--output=<path>] [--figures|--no-figures] [--detect]');
|
|
1589
|
+
errorln('Usage: construct export <markdown-file> --to=<pdf|docx|html> [--output=<path>] [--figures|--no-figures] [--plain] [--detect]');
|
|
1575
1590
|
process.exit(1);
|
|
1576
1591
|
}
|
|
1577
1592
|
if (!toFlag) {
|
|
@@ -1586,6 +1601,7 @@ async function cmdExport(args) {
|
|
|
1586
1601
|
outputPath: resolvedOut,
|
|
1587
1602
|
format: toFlag,
|
|
1588
1603
|
figures: toFlag === 'pdf' ? figures : false,
|
|
1604
|
+
branding,
|
|
1589
1605
|
repoRoot: ROOT_DIR,
|
|
1590
1606
|
});
|
|
1591
1607
|
if (!result.ok) {
|
|
@@ -1999,7 +2015,7 @@ async function _cmdPricing(args) {
|
|
|
1999
2015
|
const f = describePricingCatalogFreshness();
|
|
2000
2016
|
if (!f.present) { info(f.message); return; }
|
|
2001
2017
|
try {
|
|
2002
|
-
const cached = JSON.parse(fs.readFileSync(path.join(
|
|
2018
|
+
const cached = JSON.parse(fs.readFileSync(path.join(doctorRoot(), 'pricing-cache.json'), 'utf8'));
|
|
2003
2019
|
const divergences = checkPricingDivergence(cached.models || []);
|
|
2004
2020
|
if (divergences.length === 0) { info('No pricing divergences (>25%) between LiteLLM and the static fallback.'); return; }
|
|
2005
2021
|
println(`⚠ ${divergences.length} divergence(s):`);
|
|
@@ -2097,15 +2113,13 @@ async function printIntakeHelp() {
|
|
|
2097
2113
|
println(' construct intake metrics Show pipeline volume, velocity, and processing stats.');
|
|
2098
2114
|
println(' construct intake integrate <id> <system> Push intake to external system (github, jira, confluence).');
|
|
2099
2115
|
println(' construct intake integrate <id> <system> --create-issue Create an issue from the intake packet.');
|
|
2100
|
-
println(' construct intake config Show
|
|
2116
|
+
println(' construct intake config Show the inbox/ drop zone + scan depth.');
|
|
2101
2117
|
println(' construct intake config set --depth=N [--add-dir=PATH] [--remove-dir=PATH]');
|
|
2118
|
+
println(' Update intake config. --depth caps at the hard limit.');
|
|
2102
2119
|
println(' construct intake extraction-warnings [--since=7d] [--kind=table] [--limit=50]');
|
|
2103
2120
|
println(' Summarize ~/.cx/intake/extraction-warnings.jsonl (dropped content log).');
|
|
2104
2121
|
println(' construct intake needs-asr list List audio/video files awaiting ASR transcription.');
|
|
2105
2122
|
println(' construct intake needs-asr show <id> Show a specific ASR-pending packet.');
|
|
2106
|
-
println(' Update intake config. --depth caps at the hard limit.');
|
|
2107
|
-
println(' construct intake config set --include-project-inbox=on|off');
|
|
2108
|
-
println(' construct intake config set --include-docs-intake=on|off');
|
|
2109
2123
|
}
|
|
2110
2124
|
|
|
2111
2125
|
function parseKvFlags(args, keys) {
|
|
@@ -2599,7 +2613,6 @@ async function cmdIntakeReroute(args, cwd) {
|
|
|
2599
2613
|
|
|
2600
2614
|
async function cmdIntakeExtractionWarnings(args) {
|
|
2601
2615
|
const { createReadStream, existsSync } = await import('node:fs');
|
|
2602
|
-
const { homedir } = await import('node:os');
|
|
2603
2616
|
const { join } = await import('node:path');
|
|
2604
2617
|
const { createInterface } = await import('node:readline');
|
|
2605
2618
|
|
|
@@ -2617,7 +2630,7 @@ async function cmdIntakeExtractionWarnings(args) {
|
|
|
2617
2630
|
}
|
|
2618
2631
|
}
|
|
2619
2632
|
|
|
2620
|
-
const logPath = join(
|
|
2633
|
+
const logPath = join(doctorRoot(), 'intake', 'extraction-warnings.jsonl');
|
|
2621
2634
|
if (!existsSync(logPath)) {
|
|
2622
2635
|
println('No extraction warnings recorded yet.');
|
|
2623
2636
|
return;
|
|
@@ -2668,11 +2681,10 @@ async function cmdIntakeExtractionWarnings(args) {
|
|
|
2668
2681
|
|
|
2669
2682
|
async function cmdIntakeNeedsAsr(args) {
|
|
2670
2683
|
const { existsSync, readdirSync, readFileSync } = await import('node:fs');
|
|
2671
|
-
const { homedir } = await import('node:os');
|
|
2672
2684
|
const { join } = await import('node:path');
|
|
2673
2685
|
|
|
2674
2686
|
const sub = args[0] || 'list';
|
|
2675
|
-
const asrDir = join(
|
|
2687
|
+
const asrDir = join(doctorRoot(), 'intake', 'needs-asr');
|
|
2676
2688
|
|
|
2677
2689
|
if (sub === 'list') {
|
|
2678
2690
|
if (!existsSync(asrDir)) { println('No audio/video files awaiting ASR.'); return; }
|
|
@@ -2837,20 +2849,23 @@ async function cmdIntakeConfig(args) {
|
|
|
2837
2849
|
const cwd = process.cwd();
|
|
2838
2850
|
|
|
2839
2851
|
const action = args[0];
|
|
2852
|
+
|
|
2853
|
+
// Single-zone model (ADR-0045 §C): the canonical `inbox/` is always watched.
|
|
2854
|
+
// Only maxDepth and additionalDirs are configurable.
|
|
2855
|
+
|
|
2840
2856
|
if (!action || action === 'show') {
|
|
2841
2857
|
const cfg = loadIntakeConfig(cwd, process.env);
|
|
2842
2858
|
const desc = describeIntakeDepth(cfg.maxDepth);
|
|
2843
2859
|
println(`Intake config (root: ${cwd})`);
|
|
2844
2860
|
println('');
|
|
2845
|
-
println(
|
|
2846
|
-
println(` includeDocsIntake: ${cfg.includeDocsIntake ? 'on' : 'off'} (docs/intake)`);
|
|
2861
|
+
println(' drop zone: inbox/ (always watched)');
|
|
2847
2862
|
println(` maxDepth: ${cfg.maxDepth} — ${desc.label}`);
|
|
2848
2863
|
println(` ${desc.detail}`);
|
|
2849
2864
|
println(` hardMaxDepth: ${INTAKE_HARD_MAX_DEPTH}`);
|
|
2850
2865
|
if (cfg.parentDirs.length === 0) {
|
|
2851
|
-
println('
|
|
2866
|
+
println(' additionalDirs: (none — only inbox/ is watched)');
|
|
2852
2867
|
} else {
|
|
2853
|
-
println('
|
|
2868
|
+
println(' additionalDirs:');
|
|
2854
2869
|
for (const dir of cfg.parentDirs) println(` - ${dir}`);
|
|
2855
2870
|
}
|
|
2856
2871
|
println('');
|
|
@@ -2871,12 +2886,6 @@ async function cmdIntakeConfig(args) {
|
|
|
2871
2886
|
const depthFlag = tail.find((a) => a.startsWith('--depth='));
|
|
2872
2887
|
if (depthFlag) patch.maxDepth = Number(depthFlag.split('=')[1]);
|
|
2873
2888
|
|
|
2874
|
-
const projectFlag = tail.find((a) => a.startsWith('--include-project-inbox='));
|
|
2875
|
-
if (projectFlag) patch.includeProjectInbox = /^(on|true|1|yes)$/i.test(projectFlag.split('=')[1] || '');
|
|
2876
|
-
|
|
2877
|
-
const docsFlag = tail.find((a) => a.startsWith('--include-docs-intake='));
|
|
2878
|
-
if (docsFlag) patch.includeDocsIntake = /^(on|true|1|yes)$/i.test(docsFlag.split('=')[1] || '');
|
|
2879
|
-
|
|
2880
2889
|
const addFlags = tail.filter((a) => a.startsWith('--add-dir='));
|
|
2881
2890
|
const removeFlags = tail.filter((a) => a.startsWith('--remove-dir='));
|
|
2882
2891
|
|
|
@@ -2889,7 +2898,7 @@ async function cmdIntakeConfig(args) {
|
|
|
2889
2898
|
}
|
|
2890
2899
|
|
|
2891
2900
|
if (Object.keys(patch).length === 0) {
|
|
2892
|
-
errorln('Nothing to change. Pass --depth=N, --add-dir=PATH, --remove-dir=PATH
|
|
2901
|
+
errorln('Nothing to change. Pass --depth=N, --add-dir=PATH, or --remove-dir=PATH.');
|
|
2893
2902
|
process.exit(1);
|
|
2894
2903
|
}
|
|
2895
2904
|
|
|
@@ -2903,6 +2912,123 @@ async function cmdIntakeConfig(args) {
|
|
|
2903
2912
|
}
|
|
2904
2913
|
}
|
|
2905
2914
|
|
|
2915
|
+
async function cmdSources(args) {
|
|
2916
|
+
const {
|
|
2917
|
+
validateSourceTargets,
|
|
2918
|
+
normalizeConfigTarget,
|
|
2919
|
+
resolveEffectiveSourceTargetsFromConfig,
|
|
2920
|
+
legacyEnvSourceTargets,
|
|
2921
|
+
SOURCE_PROVIDERS,
|
|
2922
|
+
} = await import('../lib/config/source-targets.mjs');
|
|
2923
|
+
const {
|
|
2924
|
+
loadProjectConfig,
|
|
2925
|
+
writeProjectConfig,
|
|
2926
|
+
findProjectConfigPath,
|
|
2927
|
+
PROJECT_CONFIG_FILENAME,
|
|
2928
|
+
} = await import('../lib/config/project-config.mjs');
|
|
2929
|
+
|
|
2930
|
+
const cwd = process.cwd();
|
|
2931
|
+
const action = args[0] ?? 'list';
|
|
2932
|
+
|
|
2933
|
+
if (action === 'list' || action === 'show') {
|
|
2934
|
+
const { config, path: cfgPath } = loadProjectConfig(cwd, process.env);
|
|
2935
|
+
const configTargets = (config.sources?.targets ?? []).map(normalizeConfigTarget);
|
|
2936
|
+
const envTargets = legacyEnvSourceTargets(process.env);
|
|
2937
|
+
const effective = resolveEffectiveSourceTargetsFromConfig(config, process.env);
|
|
2938
|
+
println(`Source targets (config: ${cfgPath ?? 'default'})`);
|
|
2939
|
+
println('');
|
|
2940
|
+
if (configTargets.length === 0) println(' (no targets in construct.config.json)');
|
|
2941
|
+
for (const t of configTargets) {
|
|
2942
|
+
println(` ${t.id} · ${t.provider} · ${JSON.stringify(t.selector)}`);
|
|
2943
|
+
}
|
|
2944
|
+
if (envTargets.length) {
|
|
2945
|
+
println('');
|
|
2946
|
+
println('Legacy env targets (merged at runtime):');
|
|
2947
|
+
for (const t of envTargets) {
|
|
2948
|
+
println(` ${t.id} · ${t.provenance} · ${t.provider} · ${JSON.stringify(t.selector)}`);
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
println('');
|
|
2952
|
+
println(`Effective targets: ${effective.length}`);
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
if (action === 'validate') {
|
|
2957
|
+
const loaded = loadProjectConfig(cwd, process.env);
|
|
2958
|
+
const targetErrors = validateSourceTargets(loaded.raw?.sources?.targets ?? loaded.config?.sources?.targets ?? []);
|
|
2959
|
+
const all = [...(loaded.errors ?? []), ...targetErrors];
|
|
2960
|
+
if (!all.length) {
|
|
2961
|
+
info(`${loaded.path ?? PROJECT_CONFIG_FILENAME}: source targets valid`);
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
errorln(`Source target validation failed (${all.length}):`);
|
|
2965
|
+
for (const e of all) errorln(` - ${e}`);
|
|
2966
|
+
process.exit(1);
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
if (action === 'add') {
|
|
2970
|
+
const provider = args[1];
|
|
2971
|
+
const id = args[2];
|
|
2972
|
+
const selectorRaw = args[3];
|
|
2973
|
+
if (!provider || !id || !selectorRaw) {
|
|
2974
|
+
errorln('Usage: construct sources add <provider> <id> <selector-json>');
|
|
2975
|
+
process.exit(1);
|
|
2976
|
+
}
|
|
2977
|
+
if (!SOURCE_PROVIDERS.includes(provider)) {
|
|
2978
|
+
errorln(`Unknown provider: ${provider}. Expected: ${SOURCE_PROVIDERS.join(', ')}`);
|
|
2979
|
+
process.exit(1);
|
|
2980
|
+
}
|
|
2981
|
+
let selector;
|
|
2982
|
+
try { selector = JSON.parse(selectorRaw); } catch (err) {
|
|
2983
|
+
errorln(`Invalid selector JSON: ${err.message}`);
|
|
2984
|
+
process.exit(1);
|
|
2985
|
+
}
|
|
2986
|
+
const target = normalizeConfigTarget({ id, provider, selector });
|
|
2987
|
+
const targetErrors = validateSourceTargets([target]);
|
|
2988
|
+
if (targetErrors.length) {
|
|
2989
|
+
for (const e of targetErrors) errorln(` - ${e}`);
|
|
2990
|
+
process.exit(1);
|
|
2991
|
+
}
|
|
2992
|
+
const cfgPath = findProjectConfigPath(cwd) || path.join(cwd, PROJECT_CONFIG_FILENAME);
|
|
2993
|
+
const { config } = loadProjectConfig(cwd, process.env);
|
|
2994
|
+
const existing = config.sources?.targets ?? [];
|
|
2995
|
+
if (existing.some((t) => t.id === id)) {
|
|
2996
|
+
errorln(`Target id already exists: ${id}`);
|
|
2997
|
+
process.exit(1);
|
|
2998
|
+
}
|
|
2999
|
+
const next = {
|
|
3000
|
+
...config,
|
|
3001
|
+
sources: { ...(config.sources ?? {}), targets: [...existing, target] },
|
|
3002
|
+
};
|
|
3003
|
+
writeProjectConfig(cfgPath, next);
|
|
3004
|
+
info(`added source target ${id}`);
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
if (action === 'remove') {
|
|
3009
|
+
const id = args[1];
|
|
3010
|
+
if (!id) {
|
|
3011
|
+
errorln('Usage: construct sources remove <id>');
|
|
3012
|
+
process.exit(1);
|
|
3013
|
+
}
|
|
3014
|
+
const cfgPath = findProjectConfigPath(cwd) || path.join(cwd, PROJECT_CONFIG_FILENAME);
|
|
3015
|
+
const { config } = loadProjectConfig(cwd, process.env);
|
|
3016
|
+
const existing = config.sources?.targets ?? [];
|
|
3017
|
+
const nextTargets = existing.filter((t) => t.id !== id);
|
|
3018
|
+
if (nextTargets.length === existing.length) {
|
|
3019
|
+
errorln(`No target with id: ${id}`);
|
|
3020
|
+
process.exit(1);
|
|
3021
|
+
}
|
|
3022
|
+
const next = { ...config, sources: { ...(config.sources ?? {}), targets: nextTargets } };
|
|
3023
|
+
writeProjectConfig(cfgPath, next);
|
|
3024
|
+
info(`removed source target ${id}`);
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
errorln(`Unknown sources action: ${action}. Available: list, add, remove, validate`);
|
|
3029
|
+
process.exit(1);
|
|
3030
|
+
}
|
|
3031
|
+
|
|
2906
3032
|
async function cmdIntakeMetrics() {
|
|
2907
3033
|
try {
|
|
2908
3034
|
const { computeIntakeMetrics, pendingAge } = await import('../lib/embed/intake-metrics.mjs');
|
|
@@ -3882,7 +4008,7 @@ async function cmdPluginEngine(args) {
|
|
|
3882
4008
|
const [action = 'list', ...rest] = args;
|
|
3883
4009
|
const isGlobal = rest.includes('--global');
|
|
3884
4010
|
const targetPath = isGlobal
|
|
3885
|
-
? path.join(HOME, '
|
|
4011
|
+
? path.join(configDir(HOME), 'plugins.json')
|
|
3886
4012
|
: path.join(process.cwd(), '.cx', 'plugins.json');
|
|
3887
4013
|
|
|
3888
4014
|
function readPluginsJson() {
|
|
@@ -4089,7 +4215,7 @@ async function cmdEvals(args) {
|
|
|
4089
4215
|
const report = await evaluateFixture(fixturePath);
|
|
4090
4216
|
|
|
4091
4217
|
try {
|
|
4092
|
-
const cacheDir = path.join(
|
|
4218
|
+
const cacheDir = path.join(doctorRoot(), 'evals');
|
|
4093
4219
|
fs.mkdirSync(cacheDir, { recursive: true });
|
|
4094
4220
|
const ts = new Date().toISOString();
|
|
4095
4221
|
fs.writeFileSync(
|
|
@@ -4286,7 +4412,7 @@ async function cmdDocsCheck(args) {
|
|
|
4286
4412
|
println('Commands with no linked how-to in docs/README.md:');
|
|
4287
4413
|
for (const name of result.uncovered) println(` ✗ construct ${name}`);
|
|
4288
4414
|
println('');
|
|
4289
|
-
println(`Add cookbook recipes under docs/cookbook/ and link them from the cookbook index.`);
|
|
4415
|
+
println(`Add cookbook recipes under docs/guides/cookbook/ and link them from the cookbook index.`);
|
|
4290
4416
|
process.exitCode = 1;
|
|
4291
4417
|
}
|
|
4292
4418
|
}
|
|
@@ -4297,7 +4423,7 @@ async function cmdDocsSite(args = []) {
|
|
|
4297
4423
|
if (check) {
|
|
4298
4424
|
const { drift } = checkFumadocsReferenceDrift({ rootDir: ROOT_DIR });
|
|
4299
4425
|
if (drift.length === 0) {
|
|
4300
|
-
ok('docs/reference/ is up to date.');
|
|
4426
|
+
ok('docs/guides/reference/ is up to date.');
|
|
4301
4427
|
return;
|
|
4302
4428
|
}
|
|
4303
4429
|
println('');
|
|
@@ -4308,9 +4434,9 @@ async function cmdDocsSite(args = []) {
|
|
|
4308
4434
|
}
|
|
4309
4435
|
const { written } = buildFumadocsReference({ rootDir: ROOT_DIR });
|
|
4310
4436
|
if (written.length === 0) {
|
|
4311
|
-
ok('docs/reference/ already up to date.');
|
|
4437
|
+
ok('docs/guides/reference/ already up to date.');
|
|
4312
4438
|
} else {
|
|
4313
|
-
ok(`docs/reference/ regenerated (${written.length} file${written.length === 1 ? '' : 's'}).`);
|
|
4439
|
+
ok(`docs/guides/reference/ regenerated (${written.length} file${written.length === 1 ? '' : 's'}).`);
|
|
4314
4440
|
}
|
|
4315
4441
|
}
|
|
4316
4442
|
|
|
@@ -4809,7 +4935,7 @@ async function cmdProvider(args) {
|
|
|
4809
4935
|
if (!id) { errorln(`Usage: construct provider plugins ${action} <id> [<package>]`); process.exit(1); }
|
|
4810
4936
|
const isGlobal = args.includes('--global');
|
|
4811
4937
|
const targetPath = isGlobal
|
|
4812
|
-
? path.join(HOME, '
|
|
4938
|
+
? path.join(configDir(HOME), 'providers.json')
|
|
4813
4939
|
: path.join(process.cwd(), '.cx', 'providers.json');
|
|
4814
4940
|
let data = { providers: [] };
|
|
4815
4941
|
try { data = JSON.parse(fs.readFileSync(targetPath, 'utf8')); } catch { /* fresh */ }
|
|
@@ -5334,7 +5460,7 @@ async function cmdTelemetryQuery(args) {
|
|
|
5334
5460
|
println(' errors [--agent=<name>] [--since=24h] error span summary');
|
|
5335
5461
|
println(' trace <traceId> full span tree');
|
|
5336
5462
|
println('');
|
|
5337
|
-
println('For Langfuse/Honeycomb/Grafana Tempo/Datadog, see docs/concepts/observability.md');
|
|
5463
|
+
println('For Langfuse/Honeycomb/Grafana Tempo/Datadog, see docs/guides/concepts/observability.md');
|
|
5338
5464
|
}
|
|
5339
5465
|
|
|
5340
5466
|
async function cmdHook(args) {
|
|
@@ -5400,6 +5526,7 @@ const handlers = new Map([
|
|
|
5400
5526
|
['status', cmdStatus],
|
|
5401
5527
|
['install', cmdInstall],
|
|
5402
5528
|
['config', cmdConfig],
|
|
5529
|
+
['sources', cmdSources],
|
|
5403
5530
|
['intake', cmdIntake],
|
|
5404
5531
|
['recommendations', cmdRecommendations],
|
|
5405
5532
|
['integrations', cmdIntegrations],
|
|
@@ -5648,7 +5775,24 @@ const handlers = new Map([
|
|
|
5648
5775
|
['artifact', async (args) => {
|
|
5649
5776
|
const sub = args[0];
|
|
5650
5777
|
if (sub === 'validate') return runArtifactValidateCli(args.slice(1));
|
|
5651
|
-
|
|
5778
|
+
if (sub === 'workflow') {
|
|
5779
|
+
const rest = args.slice(1);
|
|
5780
|
+
const request = rest.filter((arg) => !arg.startsWith('--')).join(' ');
|
|
5781
|
+
const read = (name) => rest.find((arg) => arg.startsWith(`${name}=`))?.slice(name.length + 1);
|
|
5782
|
+
const { runArtifactWorkflow } = await import('../lib/artifact-workflow.mjs');
|
|
5783
|
+
const result = runArtifactWorkflow({
|
|
5784
|
+
input: request,
|
|
5785
|
+
artifactType: read('--type'),
|
|
5786
|
+
filePath: read('--file'),
|
|
5787
|
+
format: read('--to'),
|
|
5788
|
+
outputPath: read('--output'),
|
|
5789
|
+
branding: rest.includes('--plain') ? 'plain' : undefined,
|
|
5790
|
+
approvalMode: rest.includes('--apply') ? 'allow-durable-write' : (read('--approval-mode') || 'proposal-only'),
|
|
5791
|
+
}, { cwd: process.cwd(), rootDir: ROOT_DIR });
|
|
5792
|
+
println(JSON.stringify(result, null, 2));
|
|
5793
|
+
return;
|
|
5794
|
+
}
|
|
5795
|
+
errorln(`Unknown artifact subcommand: ${sub || '(none)'}. Available: validate, workflow`);
|
|
5652
5796
|
process.exit(1);
|
|
5653
5797
|
}],
|
|
5654
5798
|
['doc', async (args) => {
|
|
@@ -6383,7 +6527,7 @@ const handlers = new Map([
|
|
|
6383
6527
|
|
|
6384
6528
|
println('');
|
|
6385
6529
|
println(`Scaffolding a new profile draft. Three questions, then a preview.`);
|
|
6386
|
-
println(`Background: docs/concepts/persona-research.md and docs/concepts/profile-lifecycle.md`);
|
|
6530
|
+
println(`Background: docs/guides/concepts/persona-research.md and docs/guides/concepts/profile-lifecycle.md`);
|
|
6387
6531
|
println('');
|
|
6388
6532
|
if (!displayName) displayName = await ask('Display name?', id);
|
|
6389
6533
|
if (seedDepartments.length === 0) {
|
|
@@ -6442,7 +6586,7 @@ const handlers = new Map([
|
|
|
6442
6586
|
println('');
|
|
6443
6587
|
println(`✓ draft created at ${pathMod.relative(process.cwd(), result.dir)}`);
|
|
6444
6588
|
println('');
|
|
6445
|
-
println(`Next steps (in order, per docs/concepts/profile-lifecycle.md):`);
|
|
6589
|
+
println(`Next steps (in order, per docs/guides/concepts/profile-lifecycle.md):`);
|
|
6446
6590
|
println(` 1. Discover (cx-ux-researcher): fill personas/<role>.md from interviews + primary sources.`);
|
|
6447
6591
|
println(` 2. Frame (cx-product-manager): fill departments/<dept>.md charters and intake taxonomy.`);
|
|
6448
6592
|
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).
|