@geraldmaron/construct 1.0.12 → 1.0.15
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 +14 -2
- package/bin/construct +262 -14
- package/bin/construct-postinstall.mjs +23 -2
- package/commands/build/feature.md +0 -6
- package/commands/build/fix.md +0 -6
- package/commands/design/access.md +0 -6
- package/commands/design/flow.md +0 -6
- package/commands/design/ui.md +0 -6
- package/commands/measure/experiment.md +0 -6
- package/commands/measure/metrics.md +0 -6
- package/commands/measure/results.md +0 -6
- package/commands/plan/api.md +0 -6
- package/commands/plan/challenge.md +0 -6
- package/commands/plan/decide.md +0 -6
- package/commands/plan/feature.md +0 -7
- package/commands/plan/requirements.md +0 -6
- package/commands/remember/context.md +0 -6
- package/commands/remember/handoff.md +0 -6
- package/commands/remember/runbook.md +0 -6
- package/commands/review/code.md +0 -6
- package/commands/review/quality.md +0 -6
- package/commands/review/security.md +0 -6
- package/commands/ship/ready.md +0 -6
- package/commands/ship/release.md +0 -6
- package/commands/ship/status.md +0 -6
- package/commands/understand/docs.md +0 -6
- package/commands/understand/research.md +0 -6
- package/commands/understand/this.md +0 -6
- package/commands/understand/why.md +0 -6
- package/commands/work/clean.md +0 -6
- package/commands/work/drive.md +0 -6
- package/commands/work/optimize-prompts.md +0 -6
- package/commands/work/parallel-review.md +0 -6
- package/lib/auto-docs.mjs +11 -7
- package/lib/beads-client.mjs +19 -1
- package/lib/comment-lint.mjs +5 -1
- package/lib/context-state.mjs +15 -3
- package/lib/contracts/validate.mjs +54 -10
- package/lib/contracts/violation-log.mjs +154 -0
- package/lib/dashboard-static.mjs +7 -4
- package/lib/doc-stamp.mjs +16 -0
- package/lib/docs-verify.mjs +1 -8
- package/lib/doctor/watchers/bd-watch.mjs +6 -2
- package/lib/document-extract/docling-client.mjs +114 -0
- package/lib/document-extract/docling-sidecar.py +122 -0
- package/lib/document-extract/whisper-client.mjs +79 -0
- package/lib/document-extract.mjs +73 -8
- package/lib/document-ingest.mjs +26 -4
- package/lib/embed/daemon.mjs +26 -1
- package/lib/embed/docs-lifecycle.mjs +19 -0
- package/lib/embed/inbox.mjs +85 -2
- package/lib/embed/recommendation-store.mjs +29 -0
- package/lib/flavors/loader.mjs +1 -1
- package/lib/gates-audit.mjs +18 -12
- package/lib/handoffs/contract.mjs +2 -2
- package/lib/hooks/_lib/input.mjs +52 -0
- package/lib/hooks/adaptive-lint.mjs +4 -0
- package/lib/hooks/agent-tracker.mjs +59 -15
- package/lib/hooks/audit-reads.mjs +83 -42
- package/lib/hooks/audit-trail.mjs +28 -18
- package/lib/hooks/bash-output-logger.mjs +8 -2
- package/lib/hooks/block-no-verify.mjs +4 -0
- package/lib/hooks/ci-status-check.mjs +4 -0
- package/lib/hooks/comment-lint.mjs +6 -4
- package/lib/hooks/config-protection.mjs +4 -0
- package/lib/hooks/context-watch.mjs +4 -0
- package/lib/hooks/context-window-recovery.mjs +4 -0
- package/lib/hooks/dep-audit.mjs +12 -5
- package/lib/hooks/doc-coupling-check.mjs +5 -1
- package/lib/hooks/edit-accumulator.mjs +25 -10
- package/lib/hooks/edit-error-recovery.mjs +4 -0
- package/lib/hooks/edit-guard.mjs +4 -0
- package/lib/hooks/guard-bash.mjs +4 -0
- package/lib/hooks/mcp-audit.mjs +4 -0
- package/lib/hooks/mcp-health-check.mjs +4 -0
- package/lib/hooks/model-fallback.mjs +7 -11
- package/lib/hooks/policy-engine.mjs +4 -0
- package/lib/hooks/post-merge-docs-check.mjs +4 -0
- package/lib/hooks/post-merge-tracking.mjs +82 -0
- package/lib/hooks/pre-compact.mjs +4 -0
- package/lib/hooks/pre-push-gate.mjs +84 -231
- package/lib/hooks/proactive-activation.mjs +5 -2
- package/lib/hooks/readme-age-check.mjs +4 -0
- package/lib/hooks/registry-sync.mjs +35 -20
- package/lib/hooks/rule-verifier.mjs +3 -0
- package/lib/hooks/scan-secrets.mjs +4 -0
- package/lib/hooks/session-optimize.mjs +4 -0
- package/lib/hooks/session-reflect.mjs +4 -0
- package/lib/hooks/session-start.mjs +49 -2
- package/lib/hooks/session-tracking-refresh.mjs +70 -0
- package/lib/hooks/stop-notify.mjs +13 -2
- package/lib/hooks/stop-typecheck.mjs +4 -0
- package/lib/hooks/test-watch.mjs +8 -4
- package/lib/ingest/chunker.mjs +94 -0
- package/lib/ingest/pipeline.mjs +53 -0
- package/lib/ingest/store.mjs +82 -0
- package/lib/init-unified.mjs +90 -23
- package/lib/intake/attribution.mjs +77 -0
- package/lib/intake/intake-config.mjs +3 -0
- package/lib/intake/manifest.mjs +107 -0
- package/lib/intake/poll-lock.mjs +136 -0
- package/lib/intake/prepare.mjs +42 -4
- package/lib/knowledge/search.mjs +12 -0
- package/lib/logging/rotate.mjs +394 -0
- package/lib/mcp/server.mjs +142 -1
- package/lib/mcp/tools/project.mjs +2 -2
- package/lib/mcp/tools/skills.mjs +6 -3
- package/lib/mcp/tools/telemetry.mjs +1 -1
- package/lib/mcp/tools/workflow.mjs +6 -2
- package/lib/observation-store.mjs +14 -5
- package/lib/ollama-manager.mjs +32 -28
- package/lib/opencode-config.mjs +10 -3
- package/lib/orchestration/routing-tables.mjs +176 -0
- package/lib/orchestration-policy.mjs +18 -106
- package/lib/parity.mjs +48 -7
- package/lib/profiles/lifecycle.mjs +19 -1
- package/lib/project-init-shared.mjs +11 -5
- package/lib/project-root.mjs +104 -0
- package/lib/prompt-composer.js +11 -1
- package/lib/roles/catalog.mjs +1 -1
- package/lib/roles/event-bus.mjs +29 -9
- package/lib/roles/router.mjs +8 -7
- package/lib/runtime/uv-bootstrap.mjs +129 -0
- package/lib/runtime/whisper-bootstrap.mjs +102 -0
- package/lib/server/index.mjs +53 -10
- package/lib/server/static/index.html +1 -15
- package/lib/specialists/postconditions.mjs +1 -1
- package/lib/status.mjs +1 -1
- package/lib/storage/backup.mjs +2 -2
- package/lib/sync/skill-frontmatter.mjs +113 -21
- package/lib/telemetry/intent-verifications.mjs +16 -3
- package/lib/telemetry/skill-calls.mjs +12 -3
- package/lib/tracking-surfaces.mjs +377 -0
- package/lib/validators/skills.mjs +86 -54
- package/lib/worker/trace.mjs +19 -2
- package/package.json +10 -6
- package/personas/construct.md +4 -7
- package/platforms/claude/settings.template.json +29 -28
- package/rules/common/beads-hygiene.md +3 -9
- package/rules/common/code-review.md +3 -6
- package/rules/common/coding-style.md +3 -6
- package/rules/common/comments.md +3 -7
- package/rules/common/commit-approval.md +3 -6
- package/rules/common/cx-agent-routing.md +3 -7
- package/rules/common/cx-skill-routing.md +3 -3
- package/rules/common/doc-ownership.md +3 -8
- package/rules/common/efficiency.md +3 -8
- package/rules/common/framing.md +3 -8
- package/rules/common/git-workflow.md +3 -5
- package/rules/common/no-fabrication.md +4 -12
- package/rules/common/patterns.md +3 -5
- package/rules/common/release-gates.md +3 -8
- package/rules/common/research.md +3 -8
- package/rules/common/review-before-change.md +3 -9
- package/rules/common/security.md +3 -6
- package/rules/common/skill-composition.md +3 -7
- package/rules/common/testing.md +3 -6
- package/rules/golang/coding-style.md +1 -5
- package/rules/golang/hooks.md +1 -5
- package/rules/golang/patterns.md +1 -5
- package/rules/golang/security.md +1 -5
- package/rules/golang/testing.md +1 -5
- package/rules/python/coding-style.md +1 -5
- package/rules/python/hooks.md +1 -5
- package/rules/python/patterns.md +1 -5
- package/rules/python/security.md +1 -5
- package/rules/python/testing.md +1 -5
- package/rules/swift/coding-style.md +1 -5
- package/rules/swift/hooks.md +1 -5
- package/rules/swift/patterns.md +1 -5
- package/rules/swift/security.md +1 -5
- package/rules/swift/testing.md +1 -5
- package/rules/typescript/coding-style.md +1 -5
- package/rules/typescript/hooks.md +1 -5
- package/rules/typescript/patterns.md +1 -5
- package/rules/typescript/security.md +1 -5
- package/rules/typescript/testing.md +1 -5
- package/rules/web/coding-style.md +3 -5
- package/rules/web/design-quality.md +3 -5
- package/rules/web/hooks.md +3 -5
- package/rules/web/patterns.md +3 -5
- package/rules/web/performance.md +3 -5
- package/rules/web/security.md +3 -5
- package/rules/web/testing.md +3 -5
- package/scripts/sync-specialists.mjs +269 -75
- package/skills/ai/agent-dev.md +4 -5
- package/skills/ai/llm-security.md +4 -5
- package/skills/ai/ml-ops.md +4 -5
- package/skills/ai/orchestration-workflow.md +4 -5
- package/skills/ai/prompt-and-eval.md +4 -5
- package/skills/ai/prompt-optimizer.md +4 -6
- package/skills/ai/rag-system.md +4 -5
- package/skills/architecture/api-design.md +4 -5
- package/skills/architecture/caching.md +4 -5
- package/skills/architecture/cloud-native.md +4 -5
- package/skills/architecture/message-queue.md +4 -5
- package/skills/architecture/security-arch.md +4 -5
- package/skills/compliance/ai-disclosure.md +4 -5
- package/skills/compliance/data-privacy.md +4 -5
- package/skills/compliance/license-audit.md +4 -5
- package/skills/compliance/regulatory-review.md +4 -5
- package/skills/development/cpp.md +4 -5
- package/skills/development/go.md +4 -5
- package/skills/development/java.md +4 -5
- package/skills/development/kotlin.md +4 -5
- package/skills/development/mobile-crossplatform.md +4 -5
- package/skills/development/python.md +4 -5
- package/skills/development/rust.md +4 -5
- package/skills/development/shell.md +4 -5
- package/skills/development/swift.md +4 -5
- package/skills/development/typescript.md +4 -5
- package/skills/devops/ci-cd.md +4 -5
- package/skills/devops/containerization.md +4 -5
- package/skills/devops/cost-optimization.md +4 -5
- package/skills/devops/data-engineering.md +4 -5
- package/skills/devops/database.md +4 -5
- package/skills/devops/dependency-management.md +4 -5
- package/skills/devops/devsecops.md +4 -5
- package/skills/devops/git-workflow.md +4 -5
- package/skills/devops/incident-response.md +4 -5
- package/skills/devops/monorepo.md +4 -5
- package/skills/devops/observability.md +4 -5
- package/skills/devops/performance.md +4 -5
- package/skills/devops/testing.md +4 -5
- package/skills/docs/adr-workflow.md +4 -7
- package/skills/docs/backlog-proposal-workflow.md +4 -3
- package/skills/docs/customer-profile-workflow.md +4 -3
- package/skills/docs/document-ingest-workflow.md +4 -3
- package/skills/docs/evidence-ingest-workflow.md +4 -3
- package/skills/docs/init-docs.md +4 -5
- package/skills/docs/init-project.md +4 -5
- package/skills/docs/prd-workflow.md +4 -7
- package/skills/docs/prfaq-workflow.md +4 -3
- package/skills/docs/product-intelligence-review.md +4 -3
- package/skills/docs/product-intelligence-workflow.md +4 -6
- package/skills/docs/product-signal-workflow.md +4 -5
- package/skills/docs/research-workflow.md +4 -5
- package/skills/docs/runbook-workflow.md +4 -5
- package/skills/docs/strategy-workflow.md +4 -5
- package/skills/exploration/dependency-graph-reading.md +4 -7
- package/skills/exploration/repo-map.md +4 -5
- package/skills/exploration/tracer-bullet-method.md +4 -7
- package/skills/exploration/unknown-codebase-onboarding.md +4 -7
- package/skills/frameworks/django.md +4 -5
- package/skills/frameworks/nextjs.md +4 -5
- package/skills/frameworks/react.md +4 -5
- package/skills/frameworks/spring-boot.md +4 -5
- package/skills/frontend-design/accessibility.md +4 -5
- package/skills/frontend-design/component-patterns.md +4 -5
- package/skills/frontend-design/engineering.md +4 -5
- package/skills/frontend-design/state-management.md +4 -5
- package/skills/frontend-design/ui-aesthetics.md +4 -5
- package/skills/frontend-design/ux-principles.md +4 -5
- package/skills/operating/change-management.md +4 -8
- package/skills/operating/incident-response.md +4 -8
- package/skills/operating/oncall-rotation.md +4 -7
- package/skills/operating/orchestration-reference.md +4 -10
- package/skills/quality-gates/review-work.md +4 -5
- package/skills/quality-gates/verify-change.md +4 -5
- package/skills/quality-gates/verify-module.md +4 -5
- package/skills/quality-gates/verify-quality.md +4 -5
- package/skills/quality-gates/verify-security.md +4 -5
- package/skills/roles/architect.ai-systems.md +6 -9
- package/skills/roles/architect.data.md +6 -9
- package/skills/roles/architect.enterprise.md +6 -9
- package/skills/roles/architect.integration.md +6 -9
- package/skills/roles/architect.md +7 -14
- package/skills/roles/architect.platform.md +6 -9
- package/skills/roles/data-analyst.experiment.md +6 -9
- package/skills/roles/data-analyst.md +6 -9
- package/skills/roles/data-analyst.product-intelligence.md +7 -9
- package/skills/roles/data-analyst.product.md +6 -9
- package/skills/roles/data-analyst.telemetry.md +7 -9
- package/skills/roles/data-engineer.pipeline.md +6 -9
- package/skills/roles/data-engineer.vector-retrieval.md +7 -9
- package/skills/roles/data-engineer.warehouse.md +6 -9
- package/skills/roles/debugger.md +6 -9
- package/skills/roles/designer.accessibility.md +6 -9
- package/skills/roles/designer.md +7 -9
- package/skills/roles/engineer.ai.md +6 -9
- package/skills/roles/engineer.data.md +6 -9
- package/skills/roles/engineer.md +9 -9
- package/skills/roles/engineer.platform.md +6 -9
- package/skills/roles/operator.docs.md +6 -9
- package/skills/roles/operator.md +9 -9
- package/skills/roles/operator.release.md +6 -9
- package/skills/roles/operator.sre.md +6 -9
- package/skills/roles/orchestrator.md +6 -9
- package/skills/roles/product-manager.ai-product.md +6 -9
- package/skills/roles/product-manager.business-strategy.md +6 -9
- package/skills/roles/product-manager.enterprise.md +6 -9
- package/skills/roles/product-manager.growth.md +7 -9
- package/skills/roles/product-manager.md +7 -9
- package/skills/roles/product-manager.platform.md +6 -9
- package/skills/roles/product-manager.product.md +6 -9
- package/skills/roles/qa.ai-eval.md +8 -9
- package/skills/roles/qa.api-contract.md +7 -9
- package/skills/roles/qa.data-pipeline.md +7 -9
- package/skills/roles/qa.md +7 -9
- package/skills/roles/qa.test-automation.md +6 -9
- package/skills/roles/qa.web-ui.md +7 -9
- package/skills/roles/researcher.explorer.md +6 -9
- package/skills/roles/researcher.md +8 -9
- package/skills/roles/researcher.ux.md +6 -9
- package/skills/roles/reviewer.devil-advocate.md +6 -9
- package/skills/roles/reviewer.evaluator.md +6 -9
- package/skills/roles/reviewer.md +9 -9
- package/skills/roles/reviewer.trace.md +6 -9
- package/skills/roles/security.ai.md +7 -9
- package/skills/roles/security.appsec.md +6 -9
- package/skills/roles/security.cloud.md +6 -9
- package/skills/roles/security.legal-compliance.md +6 -9
- package/skills/roles/security.md +7 -9
- package/skills/roles/security.privacy.md +7 -9
- package/skills/roles/security.supply-chain.md +7 -9
- package/skills/security/blue-team.md +4 -5
- package/skills/security/code-audit.md +4 -5
- package/skills/security/pentest.md +4 -5
- package/skills/security/red-team.md +4 -5
- package/skills/security/threat-intel.md +4 -5
- package/skills/security/vuln-research.md +4 -5
- package/skills/strategy/competitive-landscape.md +4 -8
- package/skills/strategy/market-research-methods.md +4 -8
- package/skills/strategy/narrative-arc.md +4 -8
- package/skills/strategy/pricing-positioning.md +4 -8
- package/skills/utility/clean-code.md +4 -5
- package/specialists/policy-inventory.json +14 -0
- package/specialists/registry.json +158 -13
- package/lib/hooks/env-check.mjs +0 -83
- package/lib/hooks/read-tracker.mjs +0 -61
- package/lib/policy/unified-gates.mjs +0 -96
- package/lib/server/static/assets/index-ab25c707.js +0 -70
- package/lib/server/static/assets/index-f0c80a2b.css +0 -1
- package/lib/specialist-contracts-enforce.mjs +0 -158
- package/rules/common/agents.md +0 -28
- package/rules/common/development-workflow.md +0 -32
- package/rules/common/performance.md +0 -55
package/commands/review/code.md
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
commands/review/code.md (Code review) correctness, regressions, security, test coverage
|
|
3
|
-
|
|
4
|
-
Code review: correctness, regressions, security, test coverage
|
|
5
|
-
-->
|
|
6
1
|
---
|
|
7
2
|
description: "Code review: correctness, regressions, security, test coverage"
|
|
8
3
|
---
|
|
9
|
-
|
|
10
4
|
You are Construct. Review: $ARGUMENTS
|
|
11
5
|
|
|
12
6
|
If no target specified, review all uncommitted changes (`git diff HEAD`).
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
commands/review/quality.md (Quality audit) complexity, naming, duplication, dead code, maintainability
|
|
3
|
-
|
|
4
|
-
Quality audit: complexity, naming, duplication, dead code, maintainability
|
|
5
|
-
-->
|
|
6
1
|
---
|
|
7
2
|
description: "Quality audit: complexity, naming, duplication, dead code, maintainability"
|
|
8
3
|
---
|
|
9
|
-
|
|
10
4
|
You are Construct. Audit quality for: $ARGUMENTS
|
|
11
5
|
|
|
12
6
|
Check:
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
commands/review/security.md (Security scan) secrets, auth, injection, data exposure, dependency risk
|
|
3
|
-
|
|
4
|
-
Security scan: secrets, auth, injection, data exposure, dependency risk
|
|
5
|
-
-->
|
|
6
1
|
---
|
|
7
2
|
description: "Security scan: secrets, auth, injection, data exposure, dependency risk"
|
|
8
3
|
---
|
|
9
|
-
|
|
10
4
|
You are Construct. Scan: $ARGUMENTS
|
|
11
5
|
|
|
12
6
|
If no target specified, scan all uncommitted changes.
|
package/commands/ship/ready.md
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
commands/ship/ready.md (Pre-release check) is this ready to ship?
|
|
3
|
-
|
|
4
|
-
Pre-release check: is this ready to ship?
|
|
5
|
-
-->
|
|
6
1
|
---
|
|
7
2
|
description: "Pre-release check: is this ready to ship?"
|
|
8
3
|
---
|
|
9
|
-
|
|
10
4
|
You are Construct. Check if the following is ready: $ARGUMENTS
|
|
11
5
|
|
|
12
6
|
Check:
|
package/commands/ship/release.md
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
commands/ship/release.md (Release) plan rollout, changelog, rollback, and post-release verification
|
|
3
|
-
|
|
4
|
-
Release: plan rollout, changelog, rollback, and post-release verification
|
|
5
|
-
-->
|
|
6
1
|
---
|
|
7
2
|
description: "Release: plan rollout, changelog, rollback, and post-release verification"
|
|
8
3
|
---
|
|
9
|
-
|
|
10
4
|
You are Construct. Release the following: $ARGUMENTS
|
|
11
5
|
|
|
12
6
|
Readiness checklist:
|
package/commands/ship/status.md
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
commands/ship/status.md (Status) current project state, workflow, uncommitted changes, recent activity
|
|
3
|
-
|
|
4
|
-
Status: current project state, workflow, uncommitted changes, recent activity
|
|
5
|
-
-->
|
|
6
1
|
---
|
|
7
2
|
description: "Status: current project state, workflow, uncommitted changes, recent activity"
|
|
8
3
|
---
|
|
9
|
-
|
|
10
4
|
You are Construct. Report status for: $ARGUMENTS
|
|
11
5
|
|
|
12
6
|
Check and report:
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
commands/understand/docs.md (Look up documentation) current behavior from primary sources, not training memory
|
|
3
|
-
|
|
4
|
-
Look up documentation: current behavior from primary sources, not training memory
|
|
5
|
-
-->
|
|
6
1
|
---
|
|
7
2
|
description: "Look up documentation: current behavior from primary sources, not training memory"
|
|
8
3
|
---
|
|
9
|
-
|
|
10
4
|
You are Construct. Look up: $ARGUMENTS
|
|
11
5
|
|
|
12
6
|
Do not answer from training knowledge for anything library-specific, version-specific, or API-specific. Search first.
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
commands/understand/research.md. Research a topic. Verify facts from primary sources, separate evidence from inference.
|
|
3
|
-
|
|
4
|
-
Research a topic. Verify facts from primary sources, separate evidence from inference.
|
|
5
|
-
-->
|
|
6
1
|
---
|
|
7
2
|
description: Research a topic. Verify facts from primary sources, separate evidence from inference.
|
|
8
3
|
---
|
|
9
|
-
|
|
10
4
|
You are Construct. Research: $ARGUMENTS
|
|
11
5
|
|
|
12
6
|
Source hierarchy: official docs → release notes/changelogs → source code → tracked issues → community resources.
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
commands/understand/this.md (Explore and understand something) trace execution paths, map structure, gather evidence
|
|
3
|
-
|
|
4
|
-
Explore and understand something: trace execution paths, map structure, gather evidence
|
|
5
|
-
-->
|
|
6
1
|
---
|
|
7
2
|
description: "Explore and understand something: trace execution paths, map structure, gather evidence"
|
|
8
3
|
---
|
|
9
|
-
|
|
10
4
|
You are Construct. Explore and explain: $ARGUMENTS
|
|
11
5
|
|
|
12
6
|
Gather evidence: do not propose solutions unless asked.
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
commands/understand/why.md (Investigate a failure) why is this broken, what is the root cause
|
|
3
|
-
|
|
4
|
-
Investigate a failure: why is this broken, what is the root cause
|
|
5
|
-
-->
|
|
6
1
|
---
|
|
7
2
|
description: "Investigate a failure: why is this broken, what is the root cause"
|
|
8
3
|
---
|
|
9
|
-
|
|
10
4
|
You are Construct. Investigate: $ARGUMENTS
|
|
11
5
|
|
|
12
6
|
1. Capture: exact error, stack trace, reproduction steps
|
package/commands/work/clean.md
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
commands/work/clean.md (Remove AI-generated code smells) verbosity, hedging, dead comments, generic names
|
|
3
|
-
|
|
4
|
-
Remove AI-generated code smells: verbosity, hedging, dead comments, generic names
|
|
5
|
-
-->
|
|
6
1
|
---
|
|
7
2
|
description: "Remove AI-generated code smells: verbosity, hedging, dead comments, generic names"
|
|
8
3
|
---
|
|
9
|
-
|
|
10
4
|
You are cx-reviewer identifying AI slop in: $ARGUMENTS
|
|
11
5
|
|
|
12
6
|
Then dispatch cx-engineer to apply the fixes.
|
package/commands/work/drive.md
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
commands/work/drive.md (Full autonomous execution) explore, plan, implement, verify, loop until done
|
|
3
|
-
|
|
4
|
-
Full autonomous execution: explore, plan, implement, verify, loop until done
|
|
5
|
-
-->
|
|
6
1
|
---
|
|
7
2
|
description: "Full autonomous execution: explore, plan, implement, verify, loop until done"
|
|
8
3
|
---
|
|
9
|
-
|
|
10
4
|
You are Construct in drive mode. Execute $ARGUMENTS fully and autonomously without stopping for confirmation.
|
|
11
5
|
|
|
12
6
|
## Execution contract
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
commands/work/optimize-prompts.md: Prompt optimization command.
|
|
3
|
-
|
|
4
|
-
Runs the closed-loop prompt optimization workflow using telemetry trace data.
|
|
5
|
-
-->
|
|
6
1
|
---
|
|
7
2
|
description: "Closed-loop prompt optimization: read telemetry traces, diagnose failures, push improved version to staging"
|
|
8
3
|
---
|
|
9
|
-
|
|
10
4
|
You are cx-trace-reviewer running a prompt optimization cycle for: $ARGUMENTS
|
|
11
5
|
|
|
12
6
|
If $ARGUMENTS is empty, optimize all agents with median quality score below 0.65 in the past 7 days.
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
commands/work/parallel-review.md (Adversarial parallel review) 5 reviewers must agree before output ships
|
|
3
|
-
|
|
4
|
-
Adversarial parallel review: 5 reviewers must agree before output ships
|
|
5
|
-
-->
|
|
6
1
|
---
|
|
7
2
|
description: "Adversarial parallel review: 5 reviewers must agree before output ships"
|
|
8
3
|
---
|
|
9
|
-
|
|
10
4
|
You are Construct running a parallel adversarial review of: $ARGUMENTS
|
|
11
5
|
|
|
12
6
|
Dispatch the following 5 review roles concurrently:
|
package/lib/auto-docs.mjs
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* anything would change without writing files — the mode CI uses to detect drift.
|
|
11
11
|
*
|
|
12
12
|
* buildSite() is retained for compatibility; the public site now renders docs/
|
|
13
|
-
* directly through
|
|
13
|
+
* directly through Next.js (apps/docs/) and buildFumadocsReference() emits
|
|
14
|
+
* generated reference Markdown into docs/reference/.
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
import fs from 'node:fs';
|
|
@@ -82,7 +83,7 @@ function buildCoreDocsContract() {
|
|
|
82
83
|
|
|
83
84
|
const DIR_DESCRIPTIONS = {
|
|
84
85
|
agents: 'Registry and generated platform adapter chains',
|
|
85
|
-
apps: 'User-facing apps shipped from this repo (e.g. apps/docs/, the
|
|
86
|
+
apps: 'User-facing apps shipped from this repo (e.g. apps/docs/, the Next.js docs site)',
|
|
86
87
|
bin: 'CLI entrypoint (`construct`)',
|
|
87
88
|
claude: 'Claude Code integration (agents, settings template)',
|
|
88
89
|
commands: 'Command prompt assets',
|
|
@@ -151,7 +152,7 @@ function buildHooksTable(rootDir) {
|
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
function buildAgentsTable(rootDir) {
|
|
154
|
-
const registryPath = path.join(rootDir, '
|
|
155
|
+
const registryPath = path.join(rootDir, 'specialists', 'registry.json');
|
|
155
156
|
let registry;
|
|
156
157
|
try { registry = JSON.parse(fs.readFileSync(registryPath, 'utf8')); } catch { return ''; }
|
|
157
158
|
|
|
@@ -303,7 +304,7 @@ export function checkDocsCoverage({ rootDir } = {}) {
|
|
|
303
304
|
return { covered, uncovered, total: covered.length + uncovered.length };
|
|
304
305
|
}
|
|
305
306
|
|
|
306
|
-
// Categories surfaced in the
|
|
307
|
+
// Categories surfaced in the generated CLI reference. Internal commands (e.g.,
|
|
307
308
|
// `construct hook <name>`) are not user-facing reference material.
|
|
308
309
|
const REFERENCE_CATEGORIES = [
|
|
309
310
|
'Core',
|
|
@@ -407,9 +408,12 @@ function renderAgentsPage(rootDir) {
|
|
|
407
408
|
}
|
|
408
409
|
|
|
409
410
|
/**
|
|
410
|
-
* Emit MDX reference pages into docs/reference/ for the
|
|
411
|
-
* Mirrors the data sources behind AUTO regions and buildSite()
|
|
412
|
-
* Markdown files
|
|
411
|
+
* Emit MDX reference pages into docs/reference/ for the Next.js docs site
|
|
412
|
+
* in apps/docs/. Mirrors the data sources behind AUTO regions and buildSite()
|
|
413
|
+
* but writes Markdown files the docs app can pull in directly (no MkDocs
|
|
414
|
+
* intermediate). Name kept for backwards compatibility with callers that
|
|
415
|
+
* still import buildFumadocsReference; the rendering layer is now plain
|
|
416
|
+
* Next.js + @next/mdx.
|
|
413
417
|
*/
|
|
414
418
|
export function buildFumadocsReference({ rootDir } = {}) {
|
|
415
419
|
rootDir = rootDir ?? process.cwd();
|
package/lib/beads-client.mjs
CHANGED
|
@@ -28,6 +28,21 @@ import {
|
|
|
28
28
|
cleanupStaleQueue,
|
|
29
29
|
} from './beads-lock.mjs';
|
|
30
30
|
|
|
31
|
+
// Anchored bead-ID format. IDs reaching the bd shell boundary must match this;
|
|
32
|
+
// regex-extracted IDs from PR bodies, agent transcripts, or intake packets get
|
|
33
|
+
// re-validated here so a malformed value cannot reach the database. spawnSync
|
|
34
|
+
// uses argv-array form so shell injection isn't the failure mode — log
|
|
35
|
+
// corruption from a bogus ID is.
|
|
36
|
+
|
|
37
|
+
const BEAD_ID_RE = /^[a-z]+-[a-z0-9]+$/;
|
|
38
|
+
|
|
39
|
+
export function assertBeadId(id) {
|
|
40
|
+
if (typeof id !== 'string' || !BEAD_ID_RE.test(id)) {
|
|
41
|
+
throw new Error(`bd: invalid bead id ${JSON.stringify(id)} — expected prefix-suffix slug`);
|
|
42
|
+
}
|
|
43
|
+
return id;
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
import {
|
|
32
47
|
concurrentRead,
|
|
33
48
|
optimisticWrite,
|
|
@@ -253,7 +268,7 @@ async function runWithLegacyLock(args, opts, cwd, commandDesc) {
|
|
|
253
268
|
if (!lock) {
|
|
254
269
|
// Failed to get lock; add to queue if allowed
|
|
255
270
|
const maxQueueWaitMs = opts.maxQueueWaitSeconds * 1000;
|
|
256
|
-
if (Date.now() - startWait
|
|
271
|
+
if (Date.now() - startWait > maxQueueWaitMs) {
|
|
257
272
|
return {
|
|
258
273
|
success: false,
|
|
259
274
|
error: `Timeout waiting for lock after ${opts.timeoutSeconds}s`,
|
|
@@ -365,6 +380,7 @@ export async function listIssues(options = {}) {
|
|
|
365
380
|
* Uses concurrent read (no locking needed).
|
|
366
381
|
*/
|
|
367
382
|
export async function showIssue(id, options = {}) {
|
|
383
|
+
assertBeadId(id);
|
|
368
384
|
return runBdJson(['show', id], { useOptimisticLocking: true, ...options });
|
|
369
385
|
}
|
|
370
386
|
|
|
@@ -373,6 +389,7 @@ export async function showIssue(id, options = {}) {
|
|
|
373
389
|
* Uses optimistic locking for better concurrency.
|
|
374
390
|
*/
|
|
375
391
|
export async function claimIssue(id, options = {}) {
|
|
392
|
+
assertBeadId(id);
|
|
376
393
|
// Use the specialized optimistic claim function
|
|
377
394
|
const result = await claimBeadOptimistic(id, options);
|
|
378
395
|
|
|
@@ -388,6 +405,7 @@ export async function claimIssue(id, options = {}) {
|
|
|
388
405
|
* Close an issue.
|
|
389
406
|
*/
|
|
390
407
|
export async function closeIssue(id, options = {}) {
|
|
408
|
+
assertBeadId(id);
|
|
391
409
|
return runBd(['close', id], options);
|
|
392
410
|
}
|
|
393
411
|
|
package/lib/comment-lint.mjs
CHANGED
|
@@ -79,11 +79,15 @@ function requiresHeader(rel) {
|
|
|
79
79
|
|
|
80
80
|
const JS_HEADER_RE = /^(?:#![^\n]*\n)?(?:\/\/[^\n]*\n)*\/\*\*[\s\S]*?\*\//;
|
|
81
81
|
const MD_HEADER_RE = /^<!--[\s\S]*?-->/;
|
|
82
|
+
const MD_YAML_HEADER_RE = /^---\n(?:[^\n]*\n)*?description:[\s\S]*?\n---/;
|
|
82
83
|
const SH_HEADER_RE = /^#!.*\n(?:#[^\n]*\n)*/;
|
|
83
84
|
|
|
84
85
|
function hasHeader(content, type) {
|
|
85
86
|
if (type === 'js') return JS_HEADER_RE.test(content.trimStart());
|
|
86
|
-
if (type === 'md')
|
|
87
|
+
if (type === 'md') {
|
|
88
|
+
const trimmed = content.trimStart();
|
|
89
|
+
return MD_HEADER_RE.test(trimmed) || MD_YAML_HEADER_RE.test(trimmed);
|
|
90
|
+
}
|
|
87
91
|
return SH_HEADER_RE.test(content.trimStart());
|
|
88
92
|
}
|
|
89
93
|
|
package/lib/context-state.mjs
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* lib/context-state.mjs — compact project/session context persistence.
|
|
4
|
+
*
|
|
5
|
+
* Writes go through writeContextState, which uses write-to-tmp + atomic
|
|
6
|
+
* rename so concurrent writers from different hooks (pre-compact, Stop,
|
|
7
|
+
* context-window-recovery, tracking-surfaces) cannot leave a half-written
|
|
8
|
+
* JSON or markdown file behind. POSIX rename(2) is atomic for files on
|
|
9
|
+
* the same filesystem; the .cx directory always lives next to the target.
|
|
4
10
|
*/
|
|
5
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
|
|
6
12
|
import { join } from 'node:path';
|
|
7
13
|
|
|
8
14
|
export function contextJsonPath(rootDir) {
|
|
@@ -58,6 +64,12 @@ export function inspectContextState(rootDir) {
|
|
|
58
64
|
};
|
|
59
65
|
}
|
|
60
66
|
|
|
67
|
+
function atomicWrite(targetPath, content) {
|
|
68
|
+
const tmpPath = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
|
|
69
|
+
writeFileSync(tmpPath, content, 'utf8');
|
|
70
|
+
renameSync(tmpPath, targetPath);
|
|
71
|
+
}
|
|
72
|
+
|
|
61
73
|
export function writeContextState(rootDir, state, { markdown = null } = {}) {
|
|
62
74
|
const cxDir = join(rootDir, '.cx');
|
|
63
75
|
mkdirSync(cxDir, { recursive: true });
|
|
@@ -67,10 +79,10 @@ export function writeContextState(rootDir, state, { markdown = null } = {}) {
|
|
|
67
79
|
savedAt: new Date().toISOString(),
|
|
68
80
|
...state,
|
|
69
81
|
};
|
|
70
|
-
|
|
82
|
+
atomicWrite(contextJsonPath(rootDir), `${JSON.stringify(payload, null, 2)}\n`);
|
|
71
83
|
|
|
72
84
|
if (markdown !== null) {
|
|
73
|
-
|
|
85
|
+
atomicWrite(contextMarkdownPath(rootDir), markdown.endsWith('\n') ? markdown : `${markdown}\n`);
|
|
74
86
|
}
|
|
75
87
|
|
|
76
88
|
return payload;
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
import { existsSync, readFileSync } from 'node:fs';
|
|
20
20
|
import { join, dirname, resolve } from 'node:path';
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { POSTCONDITIONS, validateBinaryPostconditions } from '../specialists/postconditions.mjs';
|
|
23
|
+
import { logViolation } from './violation-log.mjs';
|
|
22
24
|
|
|
23
25
|
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
24
26
|
const CONTRACTS_PATH = join(REPO_ROOT, 'specialists', 'contracts.json');
|
|
@@ -145,28 +147,62 @@ export function findContract({ producer, consumer, id, contractsPath = CONTRACTS
|
|
|
145
147
|
* { ok: true, contract }
|
|
146
148
|
* { ok: false, status: 'BLOCKED_CONTRACT', errors[], contract }
|
|
147
149
|
*
|
|
148
|
-
* Enforcement defaults to
|
|
149
|
-
*
|
|
150
|
-
*
|
|
150
|
+
* Enforcement defaults to `block` (CISA Secure-by-Design pledge —
|
|
151
|
+
* strong-default configurations). Callers can pass `enforcement: 'warn'`
|
|
152
|
+
* explicitly per-call when they genuinely need advisory mode.
|
|
153
|
+
*
|
|
154
|
+
* Binary postconditions are enforced when the producer has a rule table in
|
|
155
|
+
* `lib/specialists/postconditions.mjs`. The check is self-enforcing: if a
|
|
156
|
+
* producer has rules and the caller does not pass a `packet`, the call is
|
|
157
|
+
* itself a contract violation (MCP best-practice 2025 — no silent skip on
|
|
158
|
+
* tool-argument omission).
|
|
151
159
|
*/
|
|
152
160
|
export function validateHandoff({
|
|
153
161
|
producer,
|
|
154
162
|
consumer,
|
|
155
163
|
id,
|
|
156
164
|
artifact,
|
|
165
|
+
packet,
|
|
157
166
|
contractsPath = CONTRACTS_PATH,
|
|
158
167
|
repoRoot = REPO_ROOT,
|
|
159
|
-
enforcement =
|
|
168
|
+
enforcement = 'block',
|
|
160
169
|
}) {
|
|
170
|
+
const errors = [];
|
|
171
|
+
const postconditionFailures = [];
|
|
172
|
+
|
|
173
|
+
// Binary postconditions are producer-bound invariants that fire regardless
|
|
174
|
+
// of whether the producer→consumer contract is registered in contracts.json.
|
|
175
|
+
// A producer with rules cannot have its packet validation skipped by
|
|
176
|
+
// claiming an unregistered consumer.
|
|
177
|
+
const hasBinaryRules = Array.isArray(POSTCONDITIONS[producer]) && POSTCONDITIONS[producer].length > 0;
|
|
178
|
+
if (hasBinaryRules) {
|
|
179
|
+
if (packet === undefined || packet === null) {
|
|
180
|
+
errors.push(`producer '${producer}' has binary postconditions; validateHandoff must be called with a packet argument`);
|
|
181
|
+
} else {
|
|
182
|
+
const binary = validateBinaryPostconditions(producer, packet);
|
|
183
|
+
for (const failure of binary.failures) {
|
|
184
|
+
errors.push(`[${failure.id}] ${failure.reason}`);
|
|
185
|
+
postconditionFailures.push(failure);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
161
190
|
const contract = findContract({ producer, consumer, id, contractsPath });
|
|
162
191
|
if (!contract) {
|
|
192
|
+
if (errors.length > 0) {
|
|
193
|
+
logViolation(`${producer}->${consumer}`, 'output', errors, packet ?? artifact, {
|
|
194
|
+
verdict: postconditionFailures.length > 0 ? 'BLOCKED_CONTRACT' : 'CONTRACT_VIOLATION',
|
|
195
|
+
postconditionFailures,
|
|
196
|
+
});
|
|
197
|
+
return enforcement === 'block'
|
|
198
|
+
? { ok: false, status: 'BLOCKED_CONTRACT', errors: [`no contract found for ${producer}→${consumer}${id ? ` id=${id}` : ''}`, ...errors], contract: null }
|
|
199
|
+
: { ok: true, warnings: [`no contract found for ${producer}→${consumer}`, ...errors], contract: null };
|
|
200
|
+
}
|
|
163
201
|
return enforcement === 'block'
|
|
164
202
|
? { ok: false, status: 'BLOCKED_CONTRACT', errors: [`no contract found for ${producer}→${consumer}${id ? ` id=${id}` : ''}`], contract: null }
|
|
165
203
|
: { ok: true, warnings: [`no contract found for ${producer}→${consumer}`], contract: null };
|
|
166
204
|
}
|
|
167
205
|
|
|
168
|
-
const errors = [];
|
|
169
|
-
|
|
170
206
|
// A handoff carries a producer's output, which is the consumer's input. The
|
|
171
207
|
// input contract is what the consumer expects to receive; check mustContain
|
|
172
208
|
// against that first. Output.schema validation only applies when the artifact
|
|
@@ -193,18 +229,26 @@ export function validateHandoff({
|
|
|
193
229
|
}
|
|
194
230
|
}
|
|
195
231
|
|
|
196
|
-
//
|
|
197
|
-
// handoff envelope declares `artifactPath`. Resolved relative to repoRoot.
|
|
232
|
+
// Disk-artifact postconditions: frontmatter, sections, citations.
|
|
198
233
|
const declaredArtifactPath = artifact?.artifactPath || artifact?.output?.artifactPath;
|
|
199
234
|
if (declaredArtifactPath) {
|
|
200
235
|
const absPath = isAbsolutePath(declaredArtifactPath)
|
|
201
236
|
? declaredArtifactPath
|
|
202
237
|
: join(repoRoot, declaredArtifactPath);
|
|
203
|
-
const pcErrors =
|
|
238
|
+
const pcErrors = validateArtifactPostconditions({ contract, artifactPath: absPath });
|
|
204
239
|
for (const e of pcErrors) errors.push(e);
|
|
205
240
|
}
|
|
206
241
|
|
|
207
242
|
if (errors.length === 0) return { ok: true, contract };
|
|
243
|
+
|
|
244
|
+
// Persist to the chain-hashed violation log so `construct doctor` and
|
|
245
|
+
// forensic replay see the failure. Best-effort; logging never throws.
|
|
246
|
+
const verdict = postconditionFailures.length > 0 ? 'BLOCKED_CONTRACT' : 'CONTRACT_VIOLATION';
|
|
247
|
+
logViolation(contract.id, 'output', errors, packet ?? artifact, {
|
|
248
|
+
verdict,
|
|
249
|
+
postconditionFailures,
|
|
250
|
+
});
|
|
251
|
+
|
|
208
252
|
if (enforcement === 'block') return { ok: false, status: 'BLOCKED_CONTRACT', errors, contract };
|
|
209
253
|
return { ok: true, warnings: errors, contract };
|
|
210
254
|
}
|
|
@@ -272,7 +316,7 @@ function claimsHaveCitations(body) {
|
|
|
272
316
|
* disk. Postconditions can be strings (descriptive, ignored here) or objects
|
|
273
317
|
* with { id, check, field?, section? }. Returns an array of error strings.
|
|
274
318
|
*/
|
|
275
|
-
export function
|
|
319
|
+
export function validateArtifactPostconditions({ contract, artifactPath }) {
|
|
276
320
|
if (!contract?.postconditions?.length) return [];
|
|
277
321
|
if (!artifactPath || !existsSync(artifactPath)) {
|
|
278
322
|
const structured = contract.postconditions.filter((p) => typeof p === 'object');
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/contracts/violation-log.mjs — tamper-evident append-only log for
|
|
3
|
+
* specialist-contract violations.
|
|
4
|
+
*
|
|
5
|
+
* Each record carries:
|
|
6
|
+
* ts — ISO timestamp
|
|
7
|
+
* sequence — monotonic per-log counter, gap-detects truncation
|
|
8
|
+
* prev_line_hash — sha256 of the prior line, gap-detects modification
|
|
9
|
+
* agent — last-known active agent (from ~/.cx/last-agent.json)
|
|
10
|
+
* contractId — the contract that fired the violation
|
|
11
|
+
* direction — 'input' | 'output'
|
|
12
|
+
* missing — field names that failed shape validation
|
|
13
|
+
* packet_keys — Object.keys(packet) when packet is an object
|
|
14
|
+
* verdict — 'CONTRACT_VIOLATION' (default) or 'BLOCKED_CONTRACT'
|
|
15
|
+
* (binary postcondition failures)
|
|
16
|
+
* postconditionFailures — [{id, reason}] when verdict is BLOCKED_CONTRACT
|
|
17
|
+
*
|
|
18
|
+
* The hash chain spans rotation: when the active file is empty (post-rotate),
|
|
19
|
+
* the tail is read from the most recent segment (gzipped or plain) via
|
|
20
|
+
* readLastLineAcrossSegments. The sequence counter follows the same path.
|
|
21
|
+
*
|
|
22
|
+
* Single owner of the file. All readers go through recentViolations or
|
|
23
|
+
* verifyChain; all writers go through logViolation.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
27
|
+
import { join, dirname } from 'node:path';
|
|
28
|
+
import { homedir } from 'node:os';
|
|
29
|
+
import { createHash } from 'node:crypto';
|
|
30
|
+
import { appendBounded, readLastLineAcrossSegments } from '../logging/rotate.mjs';
|
|
31
|
+
import { resolveProjectScopedPath } from '../project-root.mjs';
|
|
32
|
+
|
|
33
|
+
const CX_DIR = join(homedir(), '.cx');
|
|
34
|
+
const LAST_AGENT = join(CX_DIR, 'last-agent.json');
|
|
35
|
+
|
|
36
|
+
// contract-violations are PROJECT-SCOPED: resolves to
|
|
37
|
+
// <project>/.cx/contract-violations.jsonl when inside a project, falling
|
|
38
|
+
// back to ~/.cx for standalone invocations. Resolved on every call so
|
|
39
|
+
// cwd/HOME changes inside the same process (tests, harness reuse) route
|
|
40
|
+
// correctly.
|
|
41
|
+
|
|
42
|
+
function logFile() {
|
|
43
|
+
return resolveProjectScopedPath('contract-violations.jsonl', { ensureDir: false });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sha256(input) { return createHash('sha256').update(input).digest('hex'); }
|
|
47
|
+
|
|
48
|
+
function readLastAgent() {
|
|
49
|
+
try { return JSON.parse(readFileSync(LAST_AGENT, 'utf8'))?.agent || 'construct'; }
|
|
50
|
+
catch { return 'construct'; }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readTailRecord() {
|
|
54
|
+
const file = logFile();
|
|
55
|
+
const lastLine = readLastLineAcrossSegments(file);
|
|
56
|
+
if (!lastLine) return null;
|
|
57
|
+
try { return JSON.parse(lastLine); }
|
|
58
|
+
catch { return null; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readPrevLineHash() {
|
|
62
|
+
const file = logFile();
|
|
63
|
+
const lastLine = readLastLineAcrossSegments(file);
|
|
64
|
+
return lastLine ? sha256(lastLine) : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function nextSequence() {
|
|
68
|
+
const tail = readTailRecord();
|
|
69
|
+
const prior = Number.isInteger(tail?.sequence) ? tail.sequence : 0;
|
|
70
|
+
return prior + 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Append a violation record. Best-effort: file I/O failures are swallowed
|
|
75
|
+
* so logging never crashes the caller.
|
|
76
|
+
*/
|
|
77
|
+
export function logViolation(contractId, direction, missing, packet, extra = {}) {
|
|
78
|
+
try {
|
|
79
|
+
const file = logFile();
|
|
80
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
81
|
+
const record = {
|
|
82
|
+
ts: new Date().toISOString(),
|
|
83
|
+
sequence: nextSequence(),
|
|
84
|
+
agent: readLastAgent(),
|
|
85
|
+
contractId,
|
|
86
|
+
direction,
|
|
87
|
+
missing,
|
|
88
|
+
packet_keys: packet && typeof packet === 'object' ? Object.keys(packet) : null,
|
|
89
|
+
prev_line_hash: readPrevLineHash(),
|
|
90
|
+
...extra,
|
|
91
|
+
};
|
|
92
|
+
appendBounded('contract-violations', file, JSON.stringify(record) + '\n');
|
|
93
|
+
} catch { /* logging is best-effort */ }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Read recent violations from the active log segment. Returns oldest-first.
|
|
98
|
+
* Rotated segments are intentionally not included — this is the doctor
|
|
99
|
+
* surface, scoped to the active window.
|
|
100
|
+
*/
|
|
101
|
+
export function recentViolations({ windowMs = 24 * 60 * 60 * 1000 } = {}) {
|
|
102
|
+
const file = logFile();
|
|
103
|
+
if (!existsSync(file)) return [];
|
|
104
|
+
try {
|
|
105
|
+
const cutoff = Date.now() - windowMs;
|
|
106
|
+
return readFileSync(file, 'utf8')
|
|
107
|
+
.trim()
|
|
108
|
+
.split('\n')
|
|
109
|
+
.filter(Boolean)
|
|
110
|
+
.map((line) => { try { return JSON.parse(line); } catch { return null; } })
|
|
111
|
+
.filter((r) => r && new Date(r.ts).getTime() >= cutoff);
|
|
112
|
+
} catch { return []; }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Walk the active log segment, asserting:
|
|
117
|
+
* - each record's prev_line_hash matches sha256 of the prior line
|
|
118
|
+
* - sequence numbers are monotonic with no gaps
|
|
119
|
+
*
|
|
120
|
+
* Returns { ok, brokenAt? } where brokenAt is { index, reason } on first
|
|
121
|
+
* failure. An empty / missing log is `{ ok: true }` (nothing to break).
|
|
122
|
+
*/
|
|
123
|
+
export function verifyChain() {
|
|
124
|
+
const file = logFile();
|
|
125
|
+
if (!existsSync(file)) return { ok: true };
|
|
126
|
+
try {
|
|
127
|
+
const lines = readFileSync(file, 'utf8').trim().split('\n').filter(Boolean);
|
|
128
|
+
let priorLine = null;
|
|
129
|
+
let priorSeq = null;
|
|
130
|
+
for (let i = 0; i < lines.length; i++) {
|
|
131
|
+
const line = lines[i];
|
|
132
|
+
let record;
|
|
133
|
+
try { record = JSON.parse(line); }
|
|
134
|
+
catch { return { ok: false, brokenAt: { index: i, reason: 'unparseable JSON' } }; }
|
|
135
|
+
|
|
136
|
+
if (priorLine !== null) {
|
|
137
|
+
const expectedHash = sha256(priorLine);
|
|
138
|
+
if (record.prev_line_hash !== expectedHash) {
|
|
139
|
+
return { ok: false, brokenAt: { index: i, reason: 'prev_line_hash mismatch' } };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (Number.isInteger(record.sequence)) {
|
|
143
|
+
if (priorSeq !== null && record.sequence !== priorSeq + 1) {
|
|
144
|
+
return { ok: false, brokenAt: { index: i, reason: `sequence gap (expected ${priorSeq + 1}, got ${record.sequence})` } };
|
|
145
|
+
}
|
|
146
|
+
priorSeq = record.sequence;
|
|
147
|
+
}
|
|
148
|
+
priorLine = line;
|
|
149
|
+
}
|
|
150
|
+
return { ok: true };
|
|
151
|
+
} catch (err) {
|
|
152
|
+
return { ok: false, brokenAt: { index: -1, reason: err.message } };
|
|
153
|
+
}
|
|
154
|
+
}
|
package/lib/dashboard-static.mjs
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/dashboard-static.mjs — build and sync the dashboard static bundle.
|
|
3
3
|
*
|
|
4
|
-
* Keeps
|
|
5
|
-
*
|
|
4
|
+
* Keeps the Next.js static export at `apps/dashboard/out/` and the served
|
|
5
|
+
* tree at `lib/server/static/` aligned so the HTTP server picks up the
|
|
6
|
+
* current dashboard build without code changes. Pre-2.0 the dashboard
|
|
7
|
+
* lived at `dashboard/dist/`; the rebuild to Next.js (commit 84cd721)
|
|
8
|
+
* moved the source of truth into `apps/dashboard/out/`.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import fs from 'node:fs';
|
|
@@ -78,8 +81,8 @@ function removeEmptyDirs(dirPath, stopAt) {
|
|
|
78
81
|
export function getDashboardPaths({ rootDir = process.cwd() } = {}) {
|
|
79
82
|
return {
|
|
80
83
|
rootDir,
|
|
81
|
-
dashboardDir: path.join(rootDir, 'dashboard'),
|
|
82
|
-
sourceDir: path.join(rootDir, 'dashboard', '
|
|
84
|
+
dashboardDir: path.join(rootDir, 'apps', 'dashboard'),
|
|
85
|
+
sourceDir: path.join(rootDir, 'apps', 'dashboard', 'out'),
|
|
83
86
|
targetDir: path.join(rootDir, 'lib', 'server', 'static'),
|
|
84
87
|
};
|
|
85
88
|
}
|