@schneiderjoseph/devia 0.1.0
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/AGENTS.md +149 -0
- package/CHANGELOG.md +78 -0
- package/GOVERNANCE.md +51 -0
- package/LEVELS.md +78 -0
- package/LICENSE +21 -0
- package/MATURITY.md +79 -0
- package/MEMORY.md +122 -0
- package/MIGRATION.md +62 -0
- package/PRINCIPLES.md +110 -0
- package/README.md +125 -0
- package/REFERENCES.md +30 -0
- package/VERSION +5 -0
- package/bin/devia.mjs +13 -0
- package/checklists/README.md +39 -0
- package/checklists/design/accessibility-review.md +11 -0
- package/checklists/design/design-to-code-review.md +10 -0
- package/checklists/design/responsive-review.md +7 -0
- package/checklists/design/ui-review.md +8 -0
- package/checklists/design/ux-review.md +10 -0
- package/checklists/engineering/accessibility.md +8 -0
- package/checklists/engineering/api.md +12 -0
- package/checklists/engineering/architecture.md +8 -0
- package/checklists/engineering/database.md +11 -0
- package/checklists/engineering/devops.md +9 -0
- package/checklists/engineering/disaster-recovery.md +9 -0
- package/checklists/engineering/frontend.md +9 -0
- package/checklists/engineering/incident.md +9 -0
- package/checklists/engineering/performance.md +7 -0
- package/checklists/engineering/production.md +26 -0
- package/checklists/engineering/release.md +17 -0
- package/checklists/engineering/security.md +33 -0
- package/checklists/engineering/testing.md +8 -0
- package/compliance/COVERAGE.md +46 -0
- package/compliance/REQUIREMENTS.md +8 -0
- package/compliance/TRACEABILITY.md +160 -0
- package/compliance/WAIVERS.md +13 -0
- package/package.json +74 -0
- package/rules/LIFECYCLE.md +16 -0
- package/rules/README.md +166 -0
- package/rules/accessibility/A11Y-001.md +42 -0
- package/rules/accessibility/A11Y-002.md +41 -0
- package/rules/accessibility/A11Y-003.md +40 -0
- package/rules/accessibility/A11Y-004.md +40 -0
- package/rules/accessibility/A11Y-005.md +39 -0
- package/rules/accessibility/A11Y-006.md +42 -0
- package/rules/accessibility/A11Y-007.md +39 -0
- package/rules/accessibility/A11Y-008.md +40 -0
- package/rules/accessibility/A11Y-009.md +41 -0
- package/rules/accessibility/A11Y-010.md +40 -0
- package/rules/accessibility/A11Y-011.md +39 -0
- package/rules/accessibility/A11Y-012.md +39 -0
- package/rules/agent/AGT-001.md +42 -0
- package/rules/agent/AGT-002.md +39 -0
- package/rules/agent/AGT-003.md +38 -0
- package/rules/agent/AGT-004.md +39 -0
- package/rules/agent/AGT-005.md +38 -0
- package/rules/agent/AGT-006.md +38 -0
- package/rules/agent/AGT-007.md +38 -0
- package/rules/agent/AGT-008.md +38 -0
- package/rules/agent/AGT-009.md +39 -0
- package/rules/agent/AGT-010.md +38 -0
- package/rules/agent/AGT-011.md +38 -0
- package/rules/ai/AI-001.md +44 -0
- package/rules/ai/AI-002.md +39 -0
- package/rules/ai/AI-003.md +40 -0
- package/rules/ai/AI-004.md +39 -0
- package/rules/ai/AI-005.md +38 -0
- package/rules/api/API-001.md +39 -0
- package/rules/api/API-002.md +39 -0
- package/rules/api/API-003.md +39 -0
- package/rules/api/API-004.md +38 -0
- package/rules/api/API-005.md +39 -0
- package/rules/api/API-006.md +40 -0
- package/rules/architecture/ARC-001.md +41 -0
- package/rules/architecture/ARC-002.md +43 -0
- package/rules/architecture/ARC-003.md +43 -0
- package/rules/architecture/ARC-004.md +40 -0
- package/rules/architecture/ARC-005.md +39 -0
- package/rules/architecture/ARC-006.md +39 -0
- package/rules/components/CMP-001.md +39 -0
- package/rules/components/CMP-002.md +38 -0
- package/rules/components/CMP-003.md +39 -0
- package/rules/components/CMP-004.md +40 -0
- package/rules/components/CMP-005.md +39 -0
- package/rules/content/CNT-001.md +39 -0
- package/rules/data-display/DATA-001.md +39 -0
- package/rules/data-display/DATA-002.md +39 -0
- package/rules/data-display/DATA-003.md +40 -0
- package/rules/data-display/DATA-004.md +40 -0
- package/rules/database/DB-001.md +43 -0
- package/rules/database/DB-002.md +38 -0
- package/rules/database/DB-003.md +40 -0
- package/rules/database/DB-004.md +44 -0
- package/rules/database/DB-005.md +39 -0
- package/rules/database/DB-006.md +38 -0
- package/rules/database/DB-007.md +40 -0
- package/rules/database/DB-008.md +40 -0
- package/rules/design-system/DS-001.md +39 -0
- package/rules/design-system/DS-002.md +40 -0
- package/rules/design-system/DS-003.md +38 -0
- package/rules/devops/OPS-001.md +43 -0
- package/rules/devops/OPS-002.md +39 -0
- package/rules/devops/OPS-003.md +38 -0
- package/rules/devops/OPS-004.md +40 -0
- package/rules/devops/OPS-005.md +43 -0
- package/rules/devops/OPS-006.md +38 -0
- package/rules/devops/OPS-007.md +40 -0
- package/rules/interaction/INT-001.md +39 -0
- package/rules/interaction/INT-002.md +39 -0
- package/rules/localization/I18N-001.md +38 -0
- package/rules/localization/I18N-002.md +40 -0
- package/rules/localization/I18N-003.md +39 -0
- package/rules/memory/MEM-001.md +44 -0
- package/rules/memory/MEM-002.md +39 -0
- package/rules/memory/MEM-003.md +38 -0
- package/rules/memory/MEM-004.md +38 -0
- package/rules/memory/MEM-005.md +38 -0
- package/rules/memory/MEM-006.md +38 -0
- package/rules/memory/MEM-007.md +38 -0
- package/rules/memory/MEM-008.md +38 -0
- package/rules/memory/MEM-009.md +39 -0
- package/rules/memory/MEM-010.md +38 -0
- package/rules/memory/MEM-011.md +38 -0
- package/rules/motion/MOT-001.md +38 -0
- package/rules/observability/OBS-001.md +40 -0
- package/rules/observability/OBS-002.md +40 -0
- package/rules/observability/OBS-003.md +38 -0
- package/rules/observability/OBS-004.md +38 -0
- package/rules/privacy/PRIV-001.md +38 -0
- package/rules/privacy/PRIV-002.md +44 -0
- package/rules/privacy/PRIV-003.md +40 -0
- package/rules/privacy/PRIV-004.md +41 -0
- package/rules/responsive/RWD-001.md +40 -0
- package/rules/responsive/RWD-002.md +39 -0
- package/rules/responsive/RWD-003.md +39 -0
- package/rules/security/SEC-001.md +44 -0
- package/rules/security/SEC-002.md +44 -0
- package/rules/security/SEC-003.md +41 -0
- package/rules/security/SEC-004.md +40 -0
- package/rules/security/SEC-005.md +42 -0
- package/rules/security/SEC-006.md +40 -0
- package/rules/security/SEC-007.md +44 -0
- package/rules/security/SEC-008.md +39 -0
- package/rules/security/SEC-009.md +40 -0
- package/rules/security/SEC-010.md +40 -0
- package/rules/security/SEC-011.md +40 -0
- package/rules/security/SEC-012.md +40 -0
- package/rules/states/STATE-001.md +38 -0
- package/rules/states/STATE-002.md +39 -0
- package/rules/states/STATE-003.md +39 -0
- package/rules/states/STATE-004.md +38 -0
- package/rules/testing/TST-001.md +39 -0
- package/rules/testing/TST-002.md +39 -0
- package/rules/testing/TST-003.md +39 -0
- package/rules/testing/TST-004.md +38 -0
- package/rules/testing/TST-005.md +40 -0
- package/rules/testing/TST-006.md +39 -0
- package/rules/ui/UI-001.md +40 -0
- package/rules/ui/UI-002.md +38 -0
- package/rules/ui/UI-003.md +41 -0
- package/rules/ui/UI-004.md +41 -0
- package/rules/ui/UI-005.md +41 -0
- package/rules/ui/UI-006.md +40 -0
- package/rules/ui/UI-007.md +41 -0
- package/rules/ux/UX-001.md +41 -0
- package/rules/ux/UX-002.md +41 -0
- package/rules/ux/UX-003.md +39 -0
- package/rules/ux/UX-004.md +39 -0
- package/rules/ux/UX-005.md +40 -0
- package/rules/ux/UX-006.md +40 -0
- package/rules/ux/UX-007.md +44 -0
- package/rules/ux/UX-008.md +41 -0
- package/rules/ux/UX-009.md +39 -0
- package/rules/ux/UX-010.md +40 -0
- package/rules/ux/UX-011.md +40 -0
- package/rules/ux/UX-012.md +40 -0
- package/rules/ux/UX-013.md +39 -0
- package/schema/README.md +19 -0
- package/schema/checklist.schema.json +34 -0
- package/schema/component.schema.json +37 -0
- package/schema/project-config.schema.json +70 -0
- package/schema/rule.schema.json +104 -0
- package/schema/token.schema.json +15 -0
- package/schema/waiver.schema.json +33 -0
- package/scripts/build-index.mjs +178 -0
- package/scripts/validate-links.mjs +43 -0
- package/scripts/validate-rules.mjs +42 -0
- package/skills/devia/SKILL.md +114 -0
- package/src/cli.mjs +107 -0
- package/src/commands/check.mjs +483 -0
- package/src/commands/doctor.mjs +139 -0
- package/src/commands/init.mjs +186 -0
- package/src/commands/registry.mjs +154 -0
- package/src/commands/rules.mjs +101 -0
- package/src/commands/skills.mjs +97 -0
- package/src/commands/sync.mjs +83 -0
- package/src/commands/validate.mjs +194 -0
- package/src/lib/fs.mjs +89 -0
- package/src/lib/git.mjs +31 -0
- package/src/lib/rules.mjs +85 -0
- package/src/lib/ui.mjs +52 -0
- package/src/lib/vendor.mjs +74 -0
- package/src/lib/version.mjs +19 -0
- package/src/lib/yaml.mjs +152 -0
- package/standard/README.md +31 -0
- package/standard/design/README.md +51 -0
- package/standard/design/REFERENCES.md +39 -0
- package/standard/design/accessibility/aria.md +7 -0
- package/standard/design/accessibility/contrast.md +6 -0
- package/standard/design/accessibility/focus.md +6 -0
- package/standard/design/accessibility/forms.md +3 -0
- package/standard/design/accessibility/keyboard.md +6 -0
- package/standard/design/accessibility/reduced-motion.md +3 -0
- package/standard/design/accessibility/reflow.md +3 -0
- package/standard/design/accessibility/screen-readers.md +3 -0
- package/standard/design/accessibility/target-size.md +3 -0
- package/standard/design/accessibility/testing.md +3 -0
- package/standard/design/accessibility/wcag-2.2.md +16 -0
- package/standard/design/accessibility/zoom.md +3 -0
- package/standard/design/ai/ai-design-rules.md +8 -0
- package/standard/design/ai/anti-patterns.md +1 -0
- package/standard/design/ai/compliance-format.md +22 -0
- package/standard/design/ai/design-review.md +10 -0
- package/standard/design/ai/evals/README.md +1 -0
- package/standard/design/ai/instructions.md +7 -0
- package/standard/design/ai/prompts/accessibility-review.md +5 -0
- package/standard/design/ai/prompts/design-system-review.md +5 -0
- package/standard/design/ai/prompts/ui-review.md +5 -0
- package/standard/design/ai/prompts/ux-review.md +5 -0
- package/standard/design/ai/review-protocol.md +11 -0
- package/standard/design/anti-patterns/accessibility.md +7 -0
- package/standard/design/anti-patterns/ai-generated.md +6 -0
- package/standard/design/anti-patterns/dashboards.md +5 -0
- package/standard/design/anti-patterns/forms.md +5 -0
- package/standard/design/anti-patterns/responsive.md +5 -0
- package/standard/design/anti-patterns/ui.md +6 -0
- package/standard/design/anti-patterns/ux.md +5 -0
- package/standard/design/components/buttons.md +6 -0
- package/standard/design/components/dialogs.md +5 -0
- package/standard/design/components/feedback.md +3 -0
- package/standard/design/components/forms.md +3 -0
- package/standard/design/components/navigation.md +3 -0
- package/standard/design/components/tables.md +6 -0
- package/standard/design/content/error-messages.md +4 -0
- package/standard/design/content/ux-writing.md +5 -0
- package/standard/design/data-display/README.md +5 -0
- package/standard/design/data-display/currency.md +3 -0
- package/standard/design/data-display/dates.md +3 -0
- package/standard/design/data-display/density.md +3 -0
- package/standard/design/data-display/filtering.md +3 -0
- package/standard/design/data-display/localization.md +3 -0
- package/standard/design/data-display/numbers.md +3 -0
- package/standard/design/data-display/pagination.md +3 -0
- package/standard/design/data-display/percentages.md +3 -0
- package/standard/design/data-display/sorting.md +3 -0
- package/standard/design/data-display/tables.md +3 -0
- package/standard/design/data-display/time.md +3 -0
- package/standard/design/decisions/ADR-001-token-naming.md +13 -0
- package/standard/design/decisions/ADR-002-radius-scale.md +9 -0
- package/standard/design/decisions/ADR-003-color-system.md +9 -0
- package/standard/design/decisions/ADR-004-component-versioning.md +9 -0
- package/standard/design/decisions/README.md +3 -0
- package/standard/design/design-system/components.md +9 -0
- package/standard/design/design-system/naming.md +19 -0
- package/standard/design/design-system/tokens.md +7 -0
- package/standard/design/design-system/variants.md +5 -0
- package/standard/design/design-system/versioning.md +5 -0
- package/standard/design/health/coverage.md +1 -0
- package/standard/design/health/dashboard.md +14 -0
- package/standard/design/health/maturity-model.md +9 -0
- package/standard/design/health/score.md +14 -0
- package/standard/design/interaction/README.md +10 -0
- package/standard/design/interaction/keyboard.md +3 -0
- package/standard/design/interaction/mouse.md +2 -0
- package/standard/design/interaction/pen.md +1 -0
- package/standard/design/interaction/touch.md +3 -0
- package/standard/design/interaction/touchscreen.md +6 -0
- package/standard/design/interaction/voice.md +2 -0
- package/standard/design/localization/README.md +5 -0
- package/standard/design/localization/currency.md +3 -0
- package/standard/design/localization/date-time.md +3 -0
- package/standard/design/localization/i18n.md +3 -0
- package/standard/design/localization/locale-testing.md +3 -0
- package/standard/design/localization/number-formatting.md +3 -0
- package/standard/design/localization/pluralization.md +3 -0
- package/standard/design/localization/rtl.md +3 -0
- package/standard/design/localization/text-expansion.md +3 -0
- package/standard/design/motion/animation.md +6 -0
- package/standard/design/penpot/WORKFLOW.md +27 -0
- package/standard/design/platforms/README.md +12 -0
- package/standard/design/platforms/desktop/README.md +5 -0
- package/standard/design/platforms/mobile/android.md +5 -0
- package/standard/design/platforms/mobile/ios.md +5 -0
- package/standard/design/platforms/tablet/README.md +5 -0
- package/standard/design/platforms/web/README.md +1 -0
- package/standard/design/principles/design-principles.md +9 -0
- package/standard/design/principles/ui-principles.md +7 -0
- package/standard/design/principles/ux-principles.md +14 -0
- package/standard/design/references/atlassian.md +3 -0
- package/standard/design/references/carbon.md +3 -0
- package/standard/design/references/figma-sds.md +5 -0
- package/standard/design/references/govuk.md +3 -0
- package/standard/design/references/material.md +3 -0
- package/standard/design/references/nng.md +3 -0
- package/standard/design/references/openui.md +3 -0
- package/standard/design/references/primer.md +5 -0
- package/standard/design/references/storybook.md +3 -0
- package/standard/design/references/uswds.md +3 -0
- package/standard/design/references/w3c.md +3 -0
- package/standard/design/responsive/desktop.md +4 -0
- package/standard/design/responsive/mobile.md +6 -0
- package/standard/design/responsive/tablet.md +3 -0
- package/standard/design/states/async.md +3 -0
- package/standard/design/states/conflict.md +3 -0
- package/standard/design/states/empty.md +3 -0
- package/standard/design/states/error.md +3 -0
- package/standard/design/states/interaction.md +5 -0
- package/standard/design/states/loading.md +3 -0
- package/standard/design/states/offline.md +3 -0
- package/standard/design/states/optimistic.md +3 -0
- package/standard/design/states/partial.md +3 -0
- package/standard/design/states/permission-denied.md +3 -0
- package/standard/design/states/rate-limited.md +3 -0
- package/standard/design/states/read-only.md +3 -0
- package/standard/design/states/retry.md +3 -0
- package/standard/design/states/skeleton.md +3 -0
- package/standard/design/states/stale.md +3 -0
- package/standard/design/states/success.md +3 -0
- package/standard/design/states/timeout.md +3 -0
- package/standard/design/states/unsaved-changes.md +3 -0
- package/standard/design/ui/color.md +6 -0
- package/standard/design/ui/grids.md +5 -0
- package/standard/design/ui/icons.md +5 -0
- package/standard/design/ui/imagery.md +5 -0
- package/standard/design/ui/layout.md +5 -0
- package/standard/design/ui/spacing.md +5 -0
- package/standard/design/ui/typography.md +6 -0
- package/standard/design/ui/visual-design.md +7 -0
- package/standard/design/ux/cognitive-load.md +5 -0
- package/standard/design/ux/errors.md +6 -0
- package/standard/design/ux/feedback.md +5 -0
- package/standard/design/ux/forms.md +7 -0
- package/standard/design/ux/heuristics.md +14 -0
- package/standard/design/ux/information-architecture.md +6 -0
- package/standard/design/ux/navigation.md +6 -0
- package/standard/design/ux/usability.md +6 -0
- package/standard/engineering/README.md +29 -0
- package/standard/engineering/REFERENCES.md +42 -0
- package/standard/engineering/ai/AI_SECURITY.md +15 -0
- package/standard/engineering/ai/PROMPT_SECURITY.md +6 -0
- package/standard/engineering/architecture/ADR.md +11 -0
- package/standard/engineering/architecture/ARCHITECTURE.md +25 -0
- package/standard/engineering/architecture/PRINCIPLES.md +17 -0
- package/standard/engineering/architecture/THREAT_MODEL.md +14 -0
- package/standard/engineering/backend/API.md +29 -0
- package/standard/engineering/backend/BACKGROUND_JOBS.md +12 -0
- package/standard/engineering/backend/ERROR_HANDLING.md +26 -0
- package/standard/engineering/backend/LOGGING.md +12 -0
- package/standard/engineering/compliance/DATA_RETENTION.md +10 -0
- package/standard/engineering/compliance/PRIVACY.md +11 -0
- package/standard/engineering/database/BACKUPS.md +19 -0
- package/standard/engineering/database/DATABASE.md +30 -0
- package/standard/engineering/database/MIGRATIONS.md +12 -0
- package/standard/engineering/devops/BRANCH_PROTECTION.md +17 -0
- package/standard/engineering/devops/CI_CD.md +48 -0
- package/standard/engineering/devops/DEPLOYMENT.md +12 -0
- package/standard/engineering/devops/DOCKER.md +9 -0
- package/standard/engineering/devops/ENVIRONMENTS.md +12 -0
- package/standard/engineering/devops/RELEASE.md +6 -0
- package/standard/engineering/devops/ROLLBACK.md +9 -0
- package/standard/engineering/frontend/ACCESSIBILITY.md +16 -0
- package/standard/engineering/frontend/DESIGN_SYSTEM.md +10 -0
- package/standard/engineering/frontend/FRONTEND.md +17 -0
- package/standard/engineering/frontend/UX.md +15 -0
- package/standard/engineering/observability/ALERTING.md +11 -0
- package/standard/engineering/observability/INCIDENTS.md +10 -0
- package/standard/engineering/observability/MONITORING.md +17 -0
- package/standard/engineering/payments/PAYMENTS.md +15 -0
- package/standard/engineering/performance/PERFORMANCE.md +19 -0
- package/standard/engineering/security/API_SECURITY.md +22 -0
- package/standard/engineering/security/ASVS_5.0/V10_MALICIOUS_CODE.md +31 -0
- package/standard/engineering/security/ASVS_5.0/V11_BUSINESS_LOGIC.md +31 -0
- package/standard/engineering/security/ASVS_5.0/V12_FILES.md +31 -0
- package/standard/engineering/security/ASVS_5.0/V13_API.md +31 -0
- package/standard/engineering/security/ASVS_5.0/V14_CONFIGURATION.md +31 -0
- package/standard/engineering/security/ASVS_5.0/V1_ARCHITECTURE.md +31 -0
- package/standard/engineering/security/ASVS_5.0/V2_AUTHENTICATION.md +31 -0
- package/standard/engineering/security/ASVS_5.0/V3_SESSION.md +31 -0
- package/standard/engineering/security/ASVS_5.0/V4_ACCESS_CONTROL.md +31 -0
- package/standard/engineering/security/ASVS_5.0/V5_VALIDATION.md +31 -0
- package/standard/engineering/security/ASVS_5.0/V6_CRYPTOGRAPHY.md +31 -0
- package/standard/engineering/security/ASVS_5.0/V7_ERROR_HANDLING.md +31 -0
- package/standard/engineering/security/ASVS_5.0/V8_DATA_PROTECTION.md +31 -0
- package/standard/engineering/security/ASVS_5.0/V9_COMMUNICATION.md +31 -0
- package/standard/engineering/security/ASVS_REGISTRY.md +31 -0
- package/standard/engineering/security/AUTHENTICATION.md +28 -0
- package/standard/engineering/security/AUTHORIZATION.md +27 -0
- package/standard/engineering/security/DATA_SECURITY.md +14 -0
- package/standard/engineering/security/OWASP_ASVS.md +31 -0
- package/standard/engineering/security/SECRETS.md +28 -0
- package/standard/engineering/security/SECURITY.md +28 -0
- package/standard/engineering/testing/TESTING.md +27 -0
- package/templates/agents/AGENTS.md +34 -0
- package/templates/agents/CLAUDE.md +33 -0
- package/templates/agents/copilot-instructions.md +15 -0
- package/templates/agents/cursor.mdc +27 -0
- package/templates/agents/windsurfrules.md +17 -0
- package/templates/docs/ADR.md +16 -0
- package/templates/docs/FEATURE.md +19 -0
- package/templates/docs/SECURITY_REPORT.md +15 -0
- package/templates/docs/accessibility-review.md +9 -0
- package/templates/docs/component.md +9 -0
- package/templates/docs/decision-record.md +9 -0
- package/templates/docs/design-review.md +9 -0
- package/templates/docs/exception.md +9 -0
- package/templates/docs/page.md +9 -0
- package/templates/docs/pattern.md +9 -0
- package/templates/docs/user-flow.md +9 -0
- package/templates/github/workflows/app-ci.yml +91 -0
- package/templates/project/00_OVERVIEW.md +51 -0
- package/templates/project/01_ARCHITECTURE.md +37 -0
- package/templates/project/02_SURFACES.md +30 -0
- package/templates/project/03_DATA_MODEL.md +31 -0
- package/templates/project/04_PERMISSIONS.md +32 -0
- package/templates/project/05_FLOWS.md +20 -0
- package/templates/project/06_INTEGRATIONS.md +21 -0
- package/templates/project/07_DESIGN.md +33 -0
- package/templates/project/10_NEVER_ALWAYS.md +26 -0
- package/templates/project/11_GAPS.md +18 -0
- package/templates/project/12_DEBT.md +19 -0
- package/templates/project/13_RECIPES.md +35 -0
- package/templates/project/14_INDEX.md +50 -0
- package/templates/project/AGENTS.md +51 -0
- package/templates/project/README.md +49 -0
- package/templates/project/impact-map.yaml +41 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { exists, read, readJSON, writeJSON, walk } from "../lib/fs.mjs";
|
|
3
|
+
import { cliVersion, standardVersion } from "../lib/version.mjs";
|
|
4
|
+
import { vendorStandard, vendorContents } from "../lib/vendor.mjs";
|
|
5
|
+
import { color, heading, status, line } from "../lib/ui.mjs";
|
|
6
|
+
|
|
7
|
+
export default async function sync(ctx) {
|
|
8
|
+
const { root, deviaDir, flags } = ctx;
|
|
9
|
+
|
|
10
|
+
if (flags.help) {
|
|
11
|
+
line(`
|
|
12
|
+
${color.bold("devia sync")} — refresh the vendored standard in .devia/standard/
|
|
13
|
+
|
|
14
|
+
--root <dir> repository to sync
|
|
15
|
+
--dry-run report what would change, write nothing
|
|
16
|
+
|
|
17
|
+
The project memory is never touched: sync replaces the pinned copy of the standard only.
|
|
18
|
+
`.trim());
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!exists(deviaDir)) {
|
|
23
|
+
status("FAIL", "no .devia/", "run `devia init` first");
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const dest = path.join(deviaDir, "standard");
|
|
28
|
+
const installed = standardVersion();
|
|
29
|
+
const cli = cliVersion();
|
|
30
|
+
const config = readJSON(path.join(deviaDir, "devia.json")) || {};
|
|
31
|
+
const pinned = config.standardVersion;
|
|
32
|
+
|
|
33
|
+
heading("devia sync");
|
|
34
|
+
line(` ${color.dim("pinned")} ${pinned || "none"}`);
|
|
35
|
+
line(` ${color.dim("installed")} ${installed || "unknown"}`);
|
|
36
|
+
|
|
37
|
+
const before = new Map();
|
|
38
|
+
for (const rel of walk(dest)) before.set(rel, read(path.join(dest, rel)));
|
|
39
|
+
|
|
40
|
+
if (flags["dry-run"]) {
|
|
41
|
+
let changed = 0;
|
|
42
|
+
let added = 0;
|
|
43
|
+
const now = vendorContents();
|
|
44
|
+
for (const [rel, content] of now) {
|
|
45
|
+
if (!before.has(rel)) added++;
|
|
46
|
+
else if (before.get(rel) !== content) changed++;
|
|
47
|
+
}
|
|
48
|
+
const removed = [...before.keys()].filter((k) => !now.has(k) && k !== "PINNED.md").length;
|
|
49
|
+
status("INFO", `would write ${added} new, ${changed} changed, remove ${removed}`);
|
|
50
|
+
line("");
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const files = vendorStandard(dest, {
|
|
55
|
+
by: "devia sync",
|
|
56
|
+
cli,
|
|
57
|
+
standard: installed,
|
|
58
|
+
date: new Date().toISOString().slice(0, 10),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (config.standardVersion !== installed || config.deviaVersion !== cli) {
|
|
62
|
+
config.standardVersion = installed;
|
|
63
|
+
config.deviaVersion = cli;
|
|
64
|
+
writeJSON(path.join(deviaDir, "devia.json"), config);
|
|
65
|
+
status("PASS", `pin updated to ${installed}`, `written by devia ${cli}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const after = new Map();
|
|
69
|
+
for (const rel of walk(dest)) after.set(rel, read(path.join(dest, rel)));
|
|
70
|
+
const changed = [...after.keys()].filter((k) => before.has(k) && before.get(k) !== after.get(k));
|
|
71
|
+
const added = [...after.keys()].filter((k) => !before.has(k));
|
|
72
|
+
const removed = [...before.keys()].filter((k) => !after.has(k));
|
|
73
|
+
|
|
74
|
+
status("PASS", `${files} files vendored`, `v${installed}`);
|
|
75
|
+
if (added.length) status("INFO", `${added.length} new`, added.slice(0, 5).join(", "));
|
|
76
|
+
if (changed.length) status("INFO", `${changed.length} changed`, changed.slice(0, 5).join(", "));
|
|
77
|
+
if (removed.length) status("WARN", `${removed.length} removed`, removed.slice(0, 5).join(", "));
|
|
78
|
+
|
|
79
|
+
line("");
|
|
80
|
+
line(color.dim(" Read CHANGELOG.md for what moved, then run `devia check`."));
|
|
81
|
+
line("");
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { exists, read, readJSON, packageRoot } from "../lib/fs.mjs";
|
|
3
|
+
import { parseYaml } from "../lib/yaml.mjs";
|
|
4
|
+
import { standardVersion } from "../lib/version.mjs";
|
|
5
|
+
import { color, heading, status, line, summary } from "../lib/ui.mjs";
|
|
6
|
+
|
|
7
|
+
export const REQUIRED_MEMORY = [
|
|
8
|
+
"AGENTS.md",
|
|
9
|
+
"00_OVERVIEW.md",
|
|
10
|
+
"01_ARCHITECTURE.md",
|
|
11
|
+
"02_SURFACES.md",
|
|
12
|
+
"03_DATA_MODEL.md",
|
|
13
|
+
"04_PERMISSIONS.md",
|
|
14
|
+
"05_FLOWS.md",
|
|
15
|
+
"06_INTEGRATIONS.md",
|
|
16
|
+
"07_DESIGN.md",
|
|
17
|
+
"10_NEVER_ALWAYS.md",
|
|
18
|
+
"11_GAPS.md",
|
|
19
|
+
"12_DEBT.md",
|
|
20
|
+
"13_RECIPES.md",
|
|
21
|
+
"14_INDEX.md",
|
|
22
|
+
"impact-map.yaml",
|
|
23
|
+
"devia.json",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const PLACEHOLDER = /TODO\(devia\)/g;
|
|
27
|
+
|
|
28
|
+
/** Perishable facts do not belong in files that describe what the project is (MEM-007). */
|
|
29
|
+
const PERISHABLE = [
|
|
30
|
+
/\b\d+\s+tests?\s+(passing|green|failing)\b/i,
|
|
31
|
+
/\bcurrently\s+(working|building|on)\b/i,
|
|
32
|
+
/\bnext\s+(we|up|sprint|milestone)\b/i,
|
|
33
|
+
/\bas of (today|this week|yesterday)\b/i,
|
|
34
|
+
/\bin progress\b/i,
|
|
35
|
+
/\b(this|next) (sprint|week)\b/i,
|
|
36
|
+
];
|
|
37
|
+
const PERISHABLE_FILES = ["00_OVERVIEW.md", "01_ARCHITECTURE.md", "14_INDEX.md"];
|
|
38
|
+
|
|
39
|
+
/** Registry ids look like `| G12 |` (gaps) or `| D7 |` (debt). */
|
|
40
|
+
export function registryIds(text, prefix) {
|
|
41
|
+
const re = new RegExp(`^\\|\\s*(${prefix}(\\d+))\\s*\\|`, "gm");
|
|
42
|
+
const out = [];
|
|
43
|
+
let m;
|
|
44
|
+
while ((m = re.exec(text))) out.push({ id: m[1], n: Number(m[2]) });
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function checkRegistry(text, prefix, name, add) {
|
|
49
|
+
const entries = registryIds(text, prefix);
|
|
50
|
+
const seen = new Map();
|
|
51
|
+
for (const { id, n } of entries) {
|
|
52
|
+
if (seen.has(id)) add("FAIL", `${name}: duplicate id ${id}`, "ids are never reused (MEM-004)");
|
|
53
|
+
else seen.set(id, n);
|
|
54
|
+
}
|
|
55
|
+
const nums = [...seen.values()];
|
|
56
|
+
if (nums.length && new Set(nums).size !== nums.length) {
|
|
57
|
+
add("FAIL", `${name}: repeated numbering`, "MEM-004");
|
|
58
|
+
}
|
|
59
|
+
return seen.size;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default async function validate(ctx) {
|
|
63
|
+
const { root, deviaDir, flags } = ctx;
|
|
64
|
+
const results = [];
|
|
65
|
+
const add = (kind, label, detail = "") => results.push({ kind, label, detail });
|
|
66
|
+
|
|
67
|
+
if (flags.help) {
|
|
68
|
+
line(`
|
|
69
|
+
${color.bold("devia validate")} — memory integrity
|
|
70
|
+
|
|
71
|
+
--root <dir> repository to validate
|
|
72
|
+
--strict treat unfilled placeholders as failures
|
|
73
|
+
--json machine-readable output
|
|
74
|
+
`.trim());
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!exists(deviaDir)) {
|
|
79
|
+
if (ctx.json) {
|
|
80
|
+
console.log(JSON.stringify({ ok: false, error: "no .devia directory", root }, null, 2));
|
|
81
|
+
} else {
|
|
82
|
+
heading("devia validate");
|
|
83
|
+
status("FAIL", "no .devia/ in this repository", "run `devia init` (AGT-002)");
|
|
84
|
+
line("");
|
|
85
|
+
}
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 1. Structure
|
|
90
|
+
for (const file of REQUIRED_MEMORY) {
|
|
91
|
+
if (exists(path.join(deviaDir, file))) add("PASS", `memory file ${file}`);
|
|
92
|
+
else add("FAIL", `missing ${file}`, "run `devia init` to restore the template");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 2. Configuration
|
|
96
|
+
const config = readJSON(path.join(deviaDir, "devia.json"));
|
|
97
|
+
if (!config) {
|
|
98
|
+
add("FAIL", "devia.json missing or invalid JSON");
|
|
99
|
+
} else {
|
|
100
|
+
for (const key of ["project", "maturity", "standardVersion"]) {
|
|
101
|
+
if (!config[key]) add("FAIL", `devia.json: missing ${key}`);
|
|
102
|
+
}
|
|
103
|
+
if (config.project && !config.project.name) add("FAIL", "devia.json: project.name is empty");
|
|
104
|
+
const installed = standardVersion();
|
|
105
|
+
if (installed && config.standardVersion && installed !== config.standardVersion) {
|
|
106
|
+
add(
|
|
107
|
+
"WARN",
|
|
108
|
+
`standard pinned at ${config.standardVersion}, installed is ${installed}`,
|
|
109
|
+
"run `devia sync`"
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 3. Impact map
|
|
115
|
+
const mapRaw = read(path.join(deviaDir, "impact-map.yaml"));
|
|
116
|
+
if (mapRaw) {
|
|
117
|
+
const map = parseYaml(mapRaw);
|
|
118
|
+
const impacts = map.impacts || {};
|
|
119
|
+
const keys = Object.keys(impacts);
|
|
120
|
+
if (!keys.length) add("FAIL", "impact-map.yaml has no impacts", "MEM-009 cannot be checked");
|
|
121
|
+
let missing = 0;
|
|
122
|
+
for (const [key, targets] of Object.entries(impacts)) {
|
|
123
|
+
for (const t of [].concat(targets || [])) {
|
|
124
|
+
const rel = String(t);
|
|
125
|
+
if (rel.startsWith(".") || rel.includes("/")) continue; // points outside .devia
|
|
126
|
+
if (!exists(path.join(deviaDir, rel))) {
|
|
127
|
+
add("FAIL", `impact-map: ${key} -> ${rel} does not exist`);
|
|
128
|
+
missing++;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!missing && keys.length) add("PASS", `impact map: ${keys.length} change types`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 4. Registries
|
|
136
|
+
const gaps = read(path.join(deviaDir, "11_GAPS.md")) || "";
|
|
137
|
+
const debt = read(path.join(deviaDir, "12_DEBT.md")) || "";
|
|
138
|
+
const nGaps = checkRegistry(gaps, "G", "11_GAPS.md", add);
|
|
139
|
+
const nDebt = checkRegistry(debt, "D", "12_DEBT.md", add);
|
|
140
|
+
add("PASS", `registries: ${nGaps} gap ids, ${nDebt} debt ids`);
|
|
141
|
+
|
|
142
|
+
// 5. Placeholders
|
|
143
|
+
const strict = Boolean(flags.strict);
|
|
144
|
+
const current = String(config?.maturity?.current || "bronze").toLowerCase();
|
|
145
|
+
let placeholders = 0;
|
|
146
|
+
for (const file of REQUIRED_MEMORY) {
|
|
147
|
+
const text = read(path.join(deviaDir, file));
|
|
148
|
+
if (!text) continue;
|
|
149
|
+
const n = (text.match(PLACEHOLDER) || []).length;
|
|
150
|
+
if (!n) continue;
|
|
151
|
+
placeholders += n;
|
|
152
|
+
const blocking = strict || (file === "00_OVERVIEW.md" && current !== "bronze");
|
|
153
|
+
add(blocking ? "FAIL" : "WARN", `${file}: ${n} unfilled placeholder${n > 1 ? "s" : ""}`);
|
|
154
|
+
}
|
|
155
|
+
if (!placeholders) add("PASS", "no unfilled placeholders");
|
|
156
|
+
|
|
157
|
+
// 6. Perishable facts (MEM-007)
|
|
158
|
+
for (const file of PERISHABLE_FILES) {
|
|
159
|
+
const text = read(path.join(deviaDir, file));
|
|
160
|
+
if (!text) continue;
|
|
161
|
+
for (const re of PERISHABLE) {
|
|
162
|
+
const m = text.match(re);
|
|
163
|
+
if (m) add("WARN", `${file}: perishable fact "${m[0].trim()}"`, "MEM-007");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 7. Committed
|
|
168
|
+
if (exists(path.join(root, ".gitignore"))) {
|
|
169
|
+
const ignore = read(path.join(root, ".gitignore")) || "";
|
|
170
|
+
if (/^\s*\.devia\/?\s*$/m.test(ignore)) {
|
|
171
|
+
add("FAIL", ".devia/ is gitignored", "the memory is part of the repository");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const counts = results.reduce((a, r) => ((a[r.kind] = (a[r.kind] || 0) + 1), a), {});
|
|
176
|
+
const failed = counts.FAIL || 0;
|
|
177
|
+
|
|
178
|
+
if (ctx.json) {
|
|
179
|
+
console.log(JSON.stringify({ ok: failed === 0, counts, results }, null, 2));
|
|
180
|
+
return failed ? 1 : 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
heading("devia validate");
|
|
184
|
+
const quiet = !flags.verbose;
|
|
185
|
+
for (const r of results) {
|
|
186
|
+
if (quiet && r.kind === "PASS") continue;
|
|
187
|
+
status(r.kind, r.label, r.detail);
|
|
188
|
+
}
|
|
189
|
+
if (quiet && !failed && !counts.WARN) status("PASS", `${results.length} checks passed`);
|
|
190
|
+
line("");
|
|
191
|
+
line(` ${failed ? color.red("FAILED") : color.green("OK")} ${summary(counts)}`);
|
|
192
|
+
line("");
|
|
193
|
+
return failed ? 1 : 0;
|
|
194
|
+
}
|
package/src/lib/fs.mjs
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
/** Root of the installed devia package. */
|
|
6
|
+
export const packageRoot = path.resolve(
|
|
7
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
8
|
+
"..",
|
|
9
|
+
".."
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
export function exists(p) {
|
|
13
|
+
return fs.existsSync(p);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function read(p) {
|
|
17
|
+
try {
|
|
18
|
+
return fs.readFileSync(p, "utf8");
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function readJSON(p) {
|
|
25
|
+
const raw = read(p);
|
|
26
|
+
if (raw === null) return null;
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(raw);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function writeFile(p, content) {
|
|
35
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
36
|
+
fs.writeFileSync(p, content.replace(/\r\n/g, "\n"), "utf8");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function writeJSON(p, value) {
|
|
40
|
+
writeFile(p, JSON.stringify(value, null, 2) + "\n");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Recursively list files under dir, relative to dir. */
|
|
44
|
+
export function walk(dir, { filter = () => true, skip = () => false } = {}) {
|
|
45
|
+
const out = [];
|
|
46
|
+
if (!fs.existsSync(dir)) return out;
|
|
47
|
+
const stack = [""];
|
|
48
|
+
while (stack.length) {
|
|
49
|
+
const rel = stack.pop();
|
|
50
|
+
const abs = path.join(dir, rel);
|
|
51
|
+
for (const ent of fs.readdirSync(abs, { withFileTypes: true })) {
|
|
52
|
+
const childRel = rel ? path.join(rel, ent.name) : ent.name;
|
|
53
|
+
if (skip(childRel, ent)) continue;
|
|
54
|
+
if (ent.isDirectory()) stack.push(childRel);
|
|
55
|
+
else if (ent.isFile() && filter(childRel)) out.push(childRel);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return out.sort();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Copy a directory tree. Returns the number of files written. */
|
|
62
|
+
export function copyDir(from, to, { filter = () => true, overwrite = true } = {}) {
|
|
63
|
+
let n = 0;
|
|
64
|
+
for (const rel of walk(from, { filter })) {
|
|
65
|
+
const target = path.join(to, rel);
|
|
66
|
+
if (!overwrite && fs.existsSync(target)) continue;
|
|
67
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
68
|
+
fs.copyFileSync(path.join(from, rel), target);
|
|
69
|
+
n++;
|
|
70
|
+
}
|
|
71
|
+
return n;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Find the repository root by walking up for a marker, else return cwd. */
|
|
75
|
+
export function findProjectRoot(start = process.cwd()) {
|
|
76
|
+
let dir = path.resolve(start);
|
|
77
|
+
for (;;) {
|
|
78
|
+
if (
|
|
79
|
+
fs.existsSync(path.join(dir, ".devia")) ||
|
|
80
|
+
fs.existsSync(path.join(dir, ".git")) ||
|
|
81
|
+
fs.existsSync(path.join(dir, "package.json"))
|
|
82
|
+
) {
|
|
83
|
+
return dir;
|
|
84
|
+
}
|
|
85
|
+
const parent = path.dirname(dir);
|
|
86
|
+
if (parent === dir) return path.resolve(start);
|
|
87
|
+
dir = parent;
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/lib/git.mjs
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
/** Run git in `root`. Returns trimmed stdout, or null when git is absent or the command fails. */
|
|
4
|
+
export function git(root, args) {
|
|
5
|
+
try {
|
|
6
|
+
return execFileSync("git", args, {
|
|
7
|
+
cwd: root,
|
|
8
|
+
encoding: "utf8",
|
|
9
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
10
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
11
|
+
}).trim();
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isRepo(root) {
|
|
18
|
+
return git(root, ["rev-parse", "--is-inside-work-tree"]) === "true";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* What the repository actually carries under `root`: tracked files plus untracked ones that are
|
|
23
|
+
* not ignored. Ignored paths are local artefacts, not the repository's content — scanning them
|
|
24
|
+
* turns a build cache into a P0 failure. Returns null when git cannot answer, so the caller
|
|
25
|
+
* falls back to walking the tree rather than scanning nothing.
|
|
26
|
+
*/
|
|
27
|
+
export function trackedFiles(root) {
|
|
28
|
+
const out = git(root, ["ls-files", "-z", "--cached", "--others", "--exclude-standard"]);
|
|
29
|
+
if (out === null) return null;
|
|
30
|
+
return out.split("\0").filter(Boolean);
|
|
31
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { parseFrontmatter } from "./yaml.mjs";
|
|
3
|
+
import { read, walk, packageRoot } from "./fs.mjs";
|
|
4
|
+
|
|
5
|
+
export const SEVERITIES = ["MUST", "MUST NOT", "SHOULD", "MAY"];
|
|
6
|
+
export const STATUSES = [
|
|
7
|
+
"draft",
|
|
8
|
+
"proposed",
|
|
9
|
+
"active",
|
|
10
|
+
"deprecated",
|
|
11
|
+
"superseded",
|
|
12
|
+
"removed",
|
|
13
|
+
];
|
|
14
|
+
export const PRIORITIES = ["P0", "P1", "P2", "P3"];
|
|
15
|
+
export const ID_PATTERN = /^[A-Z][A-Z0-9]*(-[A-Z0-9]+)+$/;
|
|
16
|
+
|
|
17
|
+
const NOT_A_RULE = new Set(["README.md", "LIFECYCLE.md"]);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Load every rule under a `rules/` directory.
|
|
21
|
+
* Returns `{ rules, errors }`; a malformed file becomes an error, never a silent skip.
|
|
22
|
+
*/
|
|
23
|
+
export function loadRules(rulesDir = path.join(packageRoot, "rules")) {
|
|
24
|
+
const files = walk(rulesDir, {
|
|
25
|
+
filter: (rel) => rel.endsWith(".md") && !NOT_A_RULE.has(path.basename(rel)),
|
|
26
|
+
});
|
|
27
|
+
const rules = [];
|
|
28
|
+
const errors = [];
|
|
29
|
+
const seen = new Map();
|
|
30
|
+
|
|
31
|
+
for (const rel of files) {
|
|
32
|
+
const abs = path.join(rulesDir, rel);
|
|
33
|
+
const text = read(abs);
|
|
34
|
+
const { data, error } = parseFrontmatter(text ?? "");
|
|
35
|
+
const where = rel.split(path.sep).join("/");
|
|
36
|
+
|
|
37
|
+
if (error || !data) {
|
|
38
|
+
errors.push(`${where}: ${error || "missing frontmatter"}`);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const rule = { ...data, file: where };
|
|
42
|
+
const id = rule.id;
|
|
43
|
+
|
|
44
|
+
if (!id || !ID_PATTERN.test(String(id))) errors.push(`${where}: invalid id ${id}`);
|
|
45
|
+
else if (seen.has(id)) errors.push(`${where}: duplicate id ${id} (also ${seen.get(id)})`);
|
|
46
|
+
else seen.set(id, where);
|
|
47
|
+
|
|
48
|
+
if (path.basename(where, ".md") !== id) errors.push(`${where}: filename must match id`);
|
|
49
|
+
if (!SEVERITIES.includes(rule.severity)) errors.push(`${where}: bad severity ${rule.severity}`);
|
|
50
|
+
if (!STATUSES.includes(rule.status)) errors.push(`${where}: bad status ${rule.status}`);
|
|
51
|
+
if (!PRIORITIES.includes(rule.priority)) errors.push(`${where}: bad priority ${rule.priority}`);
|
|
52
|
+
if (!rule.domain) errors.push(`${where}: missing domain`);
|
|
53
|
+
if (!Array.isArray(rule.source) || rule.source.length === 0)
|
|
54
|
+
errors.push(`${where}: at least one source is required`);
|
|
55
|
+
if (!rule.requirement || String(rule.requirement).length < 10)
|
|
56
|
+
errors.push(`${where}: requirement too short`);
|
|
57
|
+
if (
|
|
58
|
+
typeof rule.validation?.automated !== "boolean" ||
|
|
59
|
+
typeof rule.validation?.manual !== "boolean"
|
|
60
|
+
)
|
|
61
|
+
errors.push(`${where}: validation.automated and validation.manual are required`);
|
|
62
|
+
|
|
63
|
+
const dir = where.split("/")[0];
|
|
64
|
+
if (rule.domain && dir !== rule.domain)
|
|
65
|
+
errors.push(`${where}: lives in ${dir}/ but declares domain ${rule.domain}`);
|
|
66
|
+
|
|
67
|
+
rules.push(rule);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
rules.sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
71
|
+
return { rules, errors };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function ruleStats(rules) {
|
|
75
|
+
const by = (key) =>
|
|
76
|
+
rules.reduce((acc, r) => ((acc[r[key]] = (acc[r[key]] || 0) + 1), acc), {});
|
|
77
|
+
return {
|
|
78
|
+
total: rules.length,
|
|
79
|
+
severity: by("severity"),
|
|
80
|
+
priority: by("priority"),
|
|
81
|
+
domain: by("domain"),
|
|
82
|
+
automatable: rules.filter((r) => r.validation?.automated).length,
|
|
83
|
+
active: rules.filter((r) => r.status === "active").length,
|
|
84
|
+
};
|
|
85
|
+
}
|
package/src/lib/ui.mjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
const useColor =
|
|
4
|
+
process.stdout.isTTY && !process.env.NO_COLOR && process.env.TERM !== "dumb";
|
|
5
|
+
|
|
6
|
+
const ESC = String.fromCharCode(27);
|
|
7
|
+
const wrap = (code) => (s) =>
|
|
8
|
+
useColor ? `${ESC}[${code}m${s}${ESC}[0m` : String(s);
|
|
9
|
+
|
|
10
|
+
export const color = {
|
|
11
|
+
bold: wrap("1"),
|
|
12
|
+
dim: wrap("2"),
|
|
13
|
+
red: wrap("31"),
|
|
14
|
+
green: wrap("32"),
|
|
15
|
+
yellow: wrap("33"),
|
|
16
|
+
blue: wrap("34"),
|
|
17
|
+
gray: wrap("90"),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function heading(text) {
|
|
21
|
+
console.log("\n" + color.bold(text));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function line(text = "") {
|
|
25
|
+
console.log(text);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const STATUS = {
|
|
29
|
+
PASS: color.green("PASS"),
|
|
30
|
+
WARN: color.yellow("WARN"),
|
|
31
|
+
FAIL: color.red("FAIL"),
|
|
32
|
+
SKIP: color.gray("SKIP"),
|
|
33
|
+
INFO: color.blue("INFO"),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function status(kind, label, detail = "") {
|
|
37
|
+
const tag = STATUS[kind] ?? kind;
|
|
38
|
+
console.log(` ${tag} ${label}${detail ? color.dim(" — " + detail) : ""}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function summary(counts) {
|
|
42
|
+
const parts = [];
|
|
43
|
+
for (const [k, v] of Object.entries(counts)) {
|
|
44
|
+
if (v) parts.push(`${v} ${k.toLowerCase()}`);
|
|
45
|
+
}
|
|
46
|
+
return parts.length ? parts.join(" · ") : "nothing to report";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function fail(message) {
|
|
50
|
+
console.error(color.red("error: ") + message);
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { packageRoot, exists, read, writeFile, copyDir, walk } from "./fs.mjs";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What `.devia/standard/` receives.
|
|
7
|
+
*
|
|
8
|
+
* A file is vendored together with whatever it links to: a relative link that resolves in this
|
|
9
|
+
* repository and not in the adopter's copy is a broken link shipped to every project
|
|
10
|
+
* (`tests/cli.test.mjs` walks the materialised tree and fails on one).
|
|
11
|
+
*
|
|
12
|
+
* `templates/project/` and `templates/agents/` are deliberately absent: their links are written
|
|
13
|
+
* for a materialised `.devia/`, so they only resolve once `init` has expanded them.
|
|
14
|
+
*/
|
|
15
|
+
export const VENDOR_FILES = [
|
|
16
|
+
"AGENTS.md",
|
|
17
|
+
"PRINCIPLES.md",
|
|
18
|
+
"MEMORY.md",
|
|
19
|
+
"LEVELS.md",
|
|
20
|
+
"MATURITY.md",
|
|
21
|
+
"GOVERNANCE.md",
|
|
22
|
+
"MIGRATION.md",
|
|
23
|
+
"REFERENCES.md",
|
|
24
|
+
"CHANGELOG.md",
|
|
25
|
+
"VERSION",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export const VENDOR_DIRS = [
|
|
29
|
+
"rules",
|
|
30
|
+
"checklists",
|
|
31
|
+
"standard",
|
|
32
|
+
"compliance",
|
|
33
|
+
"schema",
|
|
34
|
+
path.join("templates", "docs"),
|
|
35
|
+
path.join("templates", "github"),
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/** Everything the vendor would write, as `relative path -> content`. */
|
|
39
|
+
export function vendorContents() {
|
|
40
|
+
const out = new Map();
|
|
41
|
+
for (const f of VENDOR_FILES) {
|
|
42
|
+
const src = path.join(packageRoot, f);
|
|
43
|
+
if (exists(src)) out.set(f, read(src));
|
|
44
|
+
}
|
|
45
|
+
for (const d of VENDOR_DIRS) {
|
|
46
|
+
const from = path.join(packageRoot, d);
|
|
47
|
+
for (const rel of walk(from)) out.set(path.join(d, rel), read(path.join(from, rel)));
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Rewrite the pinned copy from scratch. Returns the number of files written, `PINNED.md`
|
|
54
|
+
* included.
|
|
55
|
+
*/
|
|
56
|
+
export function vendorStandard(dest, { by, cli, standard, date }) {
|
|
57
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
58
|
+
let files = 0;
|
|
59
|
+
for (const f of VENDOR_FILES) {
|
|
60
|
+
const src = path.join(packageRoot, f);
|
|
61
|
+
if (exists(src)) {
|
|
62
|
+
writeFile(path.join(dest, f), read(src));
|
|
63
|
+
files++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const d of VENDOR_DIRS) files += copyDir(path.join(packageRoot, d), path.join(dest, d));
|
|
67
|
+
writeFile(
|
|
68
|
+
path.join(dest, "PINNED.md"),
|
|
69
|
+
`# Pinned standard\n\nVendored by \`${by}\` on ${date}.\n\n` +
|
|
70
|
+
`- devia version: ${cli}\n- standard version: ${standard}\n\n` +
|
|
71
|
+
"Do not edit these files. Change the standard upstream, then run `npx devia sync`.\n"
|
|
72
|
+
);
|
|
73
|
+
return files + 1;
|
|
74
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { packageRoot, read, readJSON } from "./fs.mjs";
|
|
3
|
+
import { parseYaml } from "./yaml.mjs";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Two versions, deliberately separate: the CLI ships bug fixes without moving the standard, and
|
|
7
|
+
* the standard gains rules without a CLI change. An adopter pins the second and reports the
|
|
8
|
+
* first, so neither may stand in for the other.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Version of the installed devia CLI. */
|
|
12
|
+
export function cliVersion() {
|
|
13
|
+
return readJSON(path.join(packageRoot, "package.json"))?.version || "0.0.0";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Version of the standard corpus. */
|
|
17
|
+
export function standardVersion() {
|
|
18
|
+
return parseYaml(read(path.join(packageRoot, "VERSION")) || "").standard_version || "0.0.0";
|
|
19
|
+
}
|