@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,139 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { exists, read, readJSON, packageRoot, walk } from "../lib/fs.mjs";
|
|
3
|
+
import { git, isRepo } from "../lib/git.mjs";
|
|
4
|
+
import { standardVersion } from "../lib/version.mjs";
|
|
5
|
+
import { color, heading, status, line } from "../lib/ui.mjs";
|
|
6
|
+
import { ADAPTERS } from "./skills.mjs";
|
|
7
|
+
import { REQUIRED_MEMORY, registryIds } from "./validate.mjs";
|
|
8
|
+
|
|
9
|
+
function lastCommitTime(root, pathspec) {
|
|
10
|
+
const out = git(root, ["log", "-1", "--format=%ct", "--", ...pathspec]);
|
|
11
|
+
const n = Number(out);
|
|
12
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function commitsSince(root, sinceEpoch, pathspec) {
|
|
16
|
+
const out = git(root, [
|
|
17
|
+
"log",
|
|
18
|
+
`--since=${sinceEpoch}`,
|
|
19
|
+
"--format=%h",
|
|
20
|
+
"--",
|
|
21
|
+
...pathspec,
|
|
22
|
+
]);
|
|
23
|
+
if (out === null) return null;
|
|
24
|
+
return out ? out.split("\n").filter(Boolean).length : 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default async function doctor(ctx) {
|
|
28
|
+
const { root, deviaDir, flags } = ctx;
|
|
29
|
+
|
|
30
|
+
if (flags.help) {
|
|
31
|
+
line(`
|
|
32
|
+
${color.bold("devia doctor")} — adoption, drift and staleness
|
|
33
|
+
|
|
34
|
+
--root <dir> repository to diagnose
|
|
35
|
+
`.trim());
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
heading(`devia doctor — ${path.basename(root)}`);
|
|
40
|
+
|
|
41
|
+
// Adoption
|
|
42
|
+
if (!exists(deviaDir)) {
|
|
43
|
+
status("FAIL", "no .devia/", "run `devia init` — this is rule zero (AGT-002)");
|
|
44
|
+
line("");
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
const missing = REQUIRED_MEMORY.filter((f) => !exists(path.join(deviaDir, f)));
|
|
48
|
+
status(
|
|
49
|
+
missing.length ? "WARN" : "PASS",
|
|
50
|
+
`memory files (${REQUIRED_MEMORY.length - missing.length}/${REQUIRED_MEMORY.length})`,
|
|
51
|
+
missing.length ? `missing ${missing.join(", ")}` : ""
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Version pin
|
|
55
|
+
const config = readJSON(path.join(deviaDir, "devia.json")) || {};
|
|
56
|
+
const installed = standardVersion();
|
|
57
|
+
if (config.standardVersion && installed && config.standardVersion !== installed) {
|
|
58
|
+
status("WARN", `standard pinned at ${config.standardVersion}`, `installed ${installed} — run \`devia sync\``);
|
|
59
|
+
} else {
|
|
60
|
+
status("PASS", `standard v${config.standardVersion || installed || "?"}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// devia's own repository *is* the standard: it vendors nothing into itself (01_ARCHITECTURE.md).
|
|
64
|
+
const vendored = walk(path.join(deviaDir, "standard")).length;
|
|
65
|
+
if (path.resolve(root) === path.resolve(packageRoot)) {
|
|
66
|
+
status("SKIP", "vendored standard", "this repository is the standard");
|
|
67
|
+
} else {
|
|
68
|
+
status(
|
|
69
|
+
vendored ? "PASS" : "WARN",
|
|
70
|
+
`vendored standard: ${vendored} files`,
|
|
71
|
+
vendored ? "" : "run `devia sync`"
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Adapters
|
|
76
|
+
const present = Object.entries(ADAPTERS).filter(([, [, t]]) => exists(path.join(root, t)));
|
|
77
|
+
status(
|
|
78
|
+
present.length ? "PASS" : "WARN",
|
|
79
|
+
`agent adapters: ${present.length}/${Object.keys(ADAPTERS).length}`,
|
|
80
|
+
present.map(([k]) => k).join(", ") || "run `devia skills install`"
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Placeholders
|
|
84
|
+
let placeholders = 0;
|
|
85
|
+
for (const f of REQUIRED_MEMORY) {
|
|
86
|
+
const text = read(path.join(deviaDir, f));
|
|
87
|
+
if (text) placeholders += (text.match(/TODO\(devia\)/g) || []).length;
|
|
88
|
+
}
|
|
89
|
+
status(
|
|
90
|
+
placeholders ? "WARN" : "PASS",
|
|
91
|
+
`unfilled placeholders: ${placeholders}`,
|
|
92
|
+
placeholders ? "memory that describes nothing protects nothing" : ""
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Registries
|
|
96
|
+
const gaps = registryIds(read(path.join(deviaDir, "11_GAPS.md")) || "", "G");
|
|
97
|
+
const debt = registryIds(read(path.join(deviaDir, "12_DEBT.md")) || "", "D");
|
|
98
|
+
status("INFO", `registries: ${gaps.length} gap ids, ${debt.length} debt ids`);
|
|
99
|
+
|
|
100
|
+
// Staleness — is the memory older than the code it describes?
|
|
101
|
+
if (!isRepo(root)) {
|
|
102
|
+
status("SKIP", "staleness", "not a git repository");
|
|
103
|
+
} else {
|
|
104
|
+
const codePaths = (config.code?.paths || []).filter((p) => exists(path.join(root, p)));
|
|
105
|
+
const spec = codePaths.length ? codePaths : ["."];
|
|
106
|
+
const codeTime = lastCommitTime(root, spec);
|
|
107
|
+
const memTime = lastCommitTime(root, [".devia"]);
|
|
108
|
+
|
|
109
|
+
if (!codeTime) {
|
|
110
|
+
status("SKIP", "staleness", "no commits touching the code paths yet");
|
|
111
|
+
} else if (!memTime) {
|
|
112
|
+
status("WARN", "the memory has never been committed", "commit .devia/ with the code");
|
|
113
|
+
} else {
|
|
114
|
+
const drift = Math.max(0, codeTime - memTime);
|
|
115
|
+
const days = Math.floor(drift / 86400);
|
|
116
|
+
const behind = commitsSince(root, memTime, spec);
|
|
117
|
+
if (drift === 0) {
|
|
118
|
+
status("PASS", "memory is current with the code");
|
|
119
|
+
} else if (days < 3 && (behind ?? 0) < 5) {
|
|
120
|
+
status("PASS", `memory ${days}d behind the code`, `${behind ?? "?"} commits since`);
|
|
121
|
+
} else {
|
|
122
|
+
status(
|
|
123
|
+
"WARN",
|
|
124
|
+
`memory ${days}d behind the code`,
|
|
125
|
+
`${behind ?? "?"} commits touched ${spec.join(", ")} since the memory changed (MEM-009)`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const branch = git(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
131
|
+
const isDefault = ["main", "master"].includes(branch);
|
|
132
|
+
status(isDefault ? "WARN" : "PASS", `branch: ${branch}`, isDefault ? "work happens on a branch (OPS-002)" : "");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
line("");
|
|
136
|
+
line(color.dim(" Next: `devia validate` for integrity, `devia check` for readiness gates."));
|
|
137
|
+
line("");
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { packageRoot, exists, read, writeFile, writeJSON, walk } from "../lib/fs.mjs";
|
|
4
|
+
import { cliVersion, standardVersion } from "../lib/version.mjs";
|
|
5
|
+
import { vendorStandard } from "../lib/vendor.mjs";
|
|
6
|
+
import { color, heading, status, line } from "../lib/ui.mjs";
|
|
7
|
+
import { installAdapters, ADAPTERS } from "./skills.mjs";
|
|
8
|
+
|
|
9
|
+
const PROFILES = {
|
|
10
|
+
"web-app": "Web or SaaS application: UI, API, database",
|
|
11
|
+
service: "Backend service or API without its own UI",
|
|
12
|
+
library: "Library or package consumed by other code",
|
|
13
|
+
cli: "Command line tool",
|
|
14
|
+
"design-system": "Component library or design system",
|
|
15
|
+
docs: "Documentation or content repository",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function detectProfile(root) {
|
|
19
|
+
const pkg = path.join(root, "package.json");
|
|
20
|
+
if (exists(pkg)) {
|
|
21
|
+
const json = JSON.parse(read(pkg) || "{}");
|
|
22
|
+
const deps = { ...json.dependencies, ...json.devDependencies };
|
|
23
|
+
if (json.bin) return "cli";
|
|
24
|
+
if (deps.next || deps.react || deps.vue || deps.svelte || deps["@angular/core"])
|
|
25
|
+
return "web-app";
|
|
26
|
+
if (deps.express || deps.fastify || deps["@nestjs/core"]) return "service";
|
|
27
|
+
if (json.main || json.exports) return "library";
|
|
28
|
+
}
|
|
29
|
+
if (exists(path.join(root, "pyproject.toml")) || exists(path.join(root, "requirements.txt")))
|
|
30
|
+
return "service";
|
|
31
|
+
if (exists(path.join(root, "go.mod")) || exists(path.join(root, "Cargo.toml"))) return "service";
|
|
32
|
+
return "web-app";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function detectName(root) {
|
|
36
|
+
const pkg = path.join(root, "package.json");
|
|
37
|
+
if (exists(pkg)) {
|
|
38
|
+
try {
|
|
39
|
+
const name = JSON.parse(read(pkg)).name;
|
|
40
|
+
if (name) return String(name).replace(/^@[^/]+\//, "");
|
|
41
|
+
} catch {
|
|
42
|
+
/* fall through to the directory name */
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return path.basename(root);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function detectCodePaths(root) {
|
|
49
|
+
const candidates = ["src", "app", "lib", "packages", "server", "api", "web", "components"];
|
|
50
|
+
return candidates.filter((c) => exists(path.join(root, c)));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function fill(text, vars) {
|
|
54
|
+
return text.replace(/\{\{(\w+)\}\}/g, (m, key) => (key in vars ? vars[key] : m));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default async function init(ctx) {
|
|
58
|
+
const { root, deviaDir, flags, rootAwayFromCwd } = ctx;
|
|
59
|
+
|
|
60
|
+
if (flags.help) {
|
|
61
|
+
line(`
|
|
62
|
+
${color.bold("devia init")} — create .devia/ in this repository
|
|
63
|
+
|
|
64
|
+
--root <dir> target repository (default: detected root)
|
|
65
|
+
--profile <name> ${Object.keys(PROFILES).join(" | ")}
|
|
66
|
+
--force overwrite existing memory files (dangerous: they hold your decisions)
|
|
67
|
+
--no-agents do not write the agent adapters
|
|
68
|
+
--no-vendor do not vendor the standard into .devia/standard/
|
|
69
|
+
--yes accept a detected root that is not the current directory
|
|
70
|
+
`.trim());
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// `init` writes. When the root was detected rather than given, and it is not where the user is
|
|
75
|
+
// standing, stop and say so: a memory created in a parent repository is not something the
|
|
76
|
+
// output can undo afterwards.
|
|
77
|
+
if (rootAwayFromCwd && !flags.yes) {
|
|
78
|
+
status("FAIL", `init would write into ${root}`, "not the current directory");
|
|
79
|
+
line("");
|
|
80
|
+
line(` ${color.bold("--root .")} to create the memory here, or ${color.bold("--yes")} to accept that root.`);
|
|
81
|
+
line("");
|
|
82
|
+
return 2;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const force = Boolean(flags.force);
|
|
86
|
+
const profile = String(flags.profile || detectProfile(root));
|
|
87
|
+
if (!PROFILES[profile]) {
|
|
88
|
+
console.error(`unknown profile: ${profile}`);
|
|
89
|
+
console.error(`known profiles: ${Object.keys(PROFILES).join(", ")}`);
|
|
90
|
+
return 2;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const name = detectName(root);
|
|
94
|
+
const version = standardVersion();
|
|
95
|
+
const cli = cliVersion();
|
|
96
|
+
const vars = {
|
|
97
|
+
PROJECT_NAME: name,
|
|
98
|
+
DEVIA_VERSION: cli,
|
|
99
|
+
DATE: new Date().toISOString().slice(0, 10),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
heading(`devia init — ${name}`);
|
|
103
|
+
line(` ${color.dim("root")} ${root}`);
|
|
104
|
+
line(` ${color.dim("profile")} ${profile} — ${PROFILES[profile]}`);
|
|
105
|
+
line("");
|
|
106
|
+
|
|
107
|
+
const existed = exists(deviaDir);
|
|
108
|
+
fs.mkdirSync(deviaDir, { recursive: true });
|
|
109
|
+
|
|
110
|
+
// 1. Memory files
|
|
111
|
+
const templateDir = path.join(packageRoot, "templates", "project");
|
|
112
|
+
let written = 0;
|
|
113
|
+
let kept = 0;
|
|
114
|
+
for (const rel of walk(templateDir)) {
|
|
115
|
+
const target = path.join(deviaDir, rel);
|
|
116
|
+
if (exists(target) && !force) {
|
|
117
|
+
kept++;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
writeFile(target, fill(read(path.join(templateDir, rel)) || "", vars));
|
|
121
|
+
written++;
|
|
122
|
+
}
|
|
123
|
+
status(written ? "PASS" : "SKIP", `memory files: ${written} written`, kept ? `${kept} kept` : "");
|
|
124
|
+
|
|
125
|
+
// 2. Configuration
|
|
126
|
+
const configPath = path.join(deviaDir, "devia.json");
|
|
127
|
+
if (!exists(configPath) || force) {
|
|
128
|
+
writeJSON(configPath, {
|
|
129
|
+
deviaVersion: cli,
|
|
130
|
+
standardVersion: version,
|
|
131
|
+
project: { name, profile },
|
|
132
|
+
maturity: { target: "gold", current: "bronze" },
|
|
133
|
+
code: { paths: detectCodePaths(root) },
|
|
134
|
+
modules: {
|
|
135
|
+
engineering: true,
|
|
136
|
+
design: profile !== "service" && profile !== "cli" && profile !== "library",
|
|
137
|
+
memory: true,
|
|
138
|
+
},
|
|
139
|
+
waivers: [],
|
|
140
|
+
initializedAt: vars.DATE,
|
|
141
|
+
});
|
|
142
|
+
status("PASS", "devia.json written");
|
|
143
|
+
} else {
|
|
144
|
+
status("SKIP", "devia.json kept", "use --force to regenerate");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 3. Vendored standard
|
|
148
|
+
if (flags.vendor === false || flags["no-vendor"]) {
|
|
149
|
+
status("SKIP", "standard not vendored", "--no-vendor");
|
|
150
|
+
} else {
|
|
151
|
+
const files = vendorStandard(path.join(deviaDir, "standard"), {
|
|
152
|
+
by: "devia init",
|
|
153
|
+
cli,
|
|
154
|
+
standard: version,
|
|
155
|
+
date: vars.DATE,
|
|
156
|
+
});
|
|
157
|
+
status("PASS", `standard vendored: ${files} files`, `v${version}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 4. Agent adapters
|
|
161
|
+
if (flags.agents === false || flags["no-agents"]) {
|
|
162
|
+
status("SKIP", "agent adapters not written", "--no-agents");
|
|
163
|
+
} else {
|
|
164
|
+
const res = installAdapters(root, { force });
|
|
165
|
+
status(
|
|
166
|
+
"PASS",
|
|
167
|
+
`agent adapters: ${res.written.length} written`,
|
|
168
|
+
res.kept.length ? `kept ${res.kept.join(", ")}` : ""
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
heading("Next");
|
|
173
|
+
line(` 1. Fill ${color.bold(".devia/00_OVERVIEW.md")} from what this repository actually is`);
|
|
174
|
+
line(" 2. Move known issues into 11_GAPS.md (undecided) and 12_DEBT.md (decided, not built)");
|
|
175
|
+
line(" 3. Write the real commands into 13_RECIPES.md and .devia/AGENTS.md");
|
|
176
|
+
line(" 4. Run " + color.bold("npx devia validate") + " and " + color.bold("npx devia check"));
|
|
177
|
+
line("");
|
|
178
|
+
line(color.dim(" Commit .devia/ — it is part of the repository, not a scratch pad."));
|
|
179
|
+
if (existed && !force) {
|
|
180
|
+
line(color.dim(" Existing memory files were kept. --force overwrites them."));
|
|
181
|
+
}
|
|
182
|
+
line("");
|
|
183
|
+
return 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export { PROFILES, ADAPTERS };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { exists, read, writeFile } from "../lib/fs.mjs";
|
|
3
|
+
import { color, heading, status, line } from "../lib/ui.mjs";
|
|
4
|
+
import { registryIds } from "./validate.mjs";
|
|
5
|
+
|
|
6
|
+
const REGISTRIES = {
|
|
7
|
+
gap: {
|
|
8
|
+
file: "11_GAPS.md",
|
|
9
|
+
prefix: "G",
|
|
10
|
+
label: "gap",
|
|
11
|
+
subjectCol: 1, // ID | Question | Impact if wrong | Interim behaviour | Status
|
|
12
|
+
row: (id, text) => `| ${id} | ${text} | | | open |`,
|
|
13
|
+
rule: "MEM-001",
|
|
14
|
+
},
|
|
15
|
+
debt: {
|
|
16
|
+
file: "12_DEBT.md",
|
|
17
|
+
prefix: "D",
|
|
18
|
+
label: "debt",
|
|
19
|
+
subjectCol: 3, // ID | Rule | Where | What is missing | Priority | Opened
|
|
20
|
+
row: (id, text, flags) =>
|
|
21
|
+
`| ${id} | ${flags.rule || ""} | ${flags.where || ""} | ${text} | ${
|
|
22
|
+
String(flags.priority || "P2").toUpperCase()
|
|
23
|
+
} | ${new Date().toISOString().slice(0, 10)} |`,
|
|
24
|
+
rule: "MEM-002",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const TEMPLATE_ROW = /^\|\s*[GD]\d+\s*\|\s*TODO\(devia\)/;
|
|
29
|
+
|
|
30
|
+
function nextId(text, prefix) {
|
|
31
|
+
const ids = registryIds(text, prefix);
|
|
32
|
+
const max = ids.reduce((a, r) => Math.max(a, r.n), 0);
|
|
33
|
+
return `${prefix}${max + 1}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Insert a row after the last row of the first table in the file. */
|
|
37
|
+
function insertRow(text, row) {
|
|
38
|
+
const lines = text.split("\n");
|
|
39
|
+
let lastRow = -1;
|
|
40
|
+
for (let i = 0; i < lines.length; i++) {
|
|
41
|
+
if (/^\|/.test(lines[i])) lastRow = i;
|
|
42
|
+
if (/^##\s+(Closed|Discharged)/i.test(lines[i])) break;
|
|
43
|
+
}
|
|
44
|
+
if (lastRow < 0) return text.trimEnd() + "\n\n" + row + "\n";
|
|
45
|
+
lines.splice(lastRow + 1, 0, row);
|
|
46
|
+
return lines.join("\n");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default async function registry(ctx, command) {
|
|
50
|
+
const { deviaDir, args, flags } = ctx;
|
|
51
|
+
const spec = REGISTRIES[command];
|
|
52
|
+
const action = args._[1];
|
|
53
|
+
const text = args._.slice(2).join(" ").trim();
|
|
54
|
+
|
|
55
|
+
if (flags.help || !action || (action === "add" && !text)) {
|
|
56
|
+
line(`
|
|
57
|
+
${color.bold(`devia ${command}`)} — the ${spec.label} registry (${spec.rule})
|
|
58
|
+
|
|
59
|
+
devia ${command} add "<what>" add a line with the next id
|
|
60
|
+
devia ${command} list show the open lines
|
|
61
|
+
devia ${command} close <ID> "<by>" move a line to the closed section
|
|
62
|
+
|
|
63
|
+
${command === "debt" ? " --rule <ID> --where <path> --priority P0|P1|P2|P3\n" : ""}
|
|
64
|
+
A line is never deleted by a change that did not discharge it (MEM-003, MEM-011).
|
|
65
|
+
`.trim());
|
|
66
|
+
return flags.help ? 0 : 2;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const file = path.join(deviaDir, spec.file);
|
|
70
|
+
if (!exists(file)) {
|
|
71
|
+
status("FAIL", `${spec.file} not found`, "run `devia init`");
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
let content = read(file) || "";
|
|
75
|
+
|
|
76
|
+
if (action === "list") {
|
|
77
|
+
heading(`open ${spec.label} lines`);
|
|
78
|
+
const rows = [];
|
|
79
|
+
for (const l of content.split("\n")) {
|
|
80
|
+
if (/^##\s+(Closed|Discharged)/i.test(l)) break;
|
|
81
|
+
if (new RegExp(`^\\|\\s*${spec.prefix}\\d+\\s*\\|`).test(l)) rows.push(l);
|
|
82
|
+
}
|
|
83
|
+
if (!rows.length) status("INFO", "no open lines");
|
|
84
|
+
for (const r of rows) line(" " + r);
|
|
85
|
+
line("");
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (action === "add") {
|
|
90
|
+
// A template row is a placeholder, not a record: the first real line replaces it.
|
|
91
|
+
const lines = content.split("\n");
|
|
92
|
+
const templateAt = lines.findIndex((l) => TEMPLATE_ROW.test(l));
|
|
93
|
+
if (templateAt >= 0) lines.splice(templateAt, 1);
|
|
94
|
+
content = lines.join("\n");
|
|
95
|
+
|
|
96
|
+
const id = nextId(content, spec.prefix);
|
|
97
|
+
content = insertRow(content, spec.row(id, text, flags));
|
|
98
|
+
writeFile(file, content);
|
|
99
|
+
heading(`${spec.label} added`);
|
|
100
|
+
status("PASS", id, text);
|
|
101
|
+
line(color.dim(` ${spec.file} — verify the claim before you trust it (MEM-005)`));
|
|
102
|
+
line("");
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (action === "close") {
|
|
107
|
+
const id = String(args._[1 + 1] || "").toUpperCase();
|
|
108
|
+
const by = args._.slice(3).join(" ").trim();
|
|
109
|
+
if (!id || !by) {
|
|
110
|
+
status("FAIL", "usage", `devia ${command} close <ID> "closed by <what>"`);
|
|
111
|
+
return 2;
|
|
112
|
+
}
|
|
113
|
+
// Splice the row out rather than blanking it: a blank line ends a markdown table, so every
|
|
114
|
+
// row below the closed one would render as loose text instead.
|
|
115
|
+
const rowRe = new RegExp(`^\\|\\s*${id}\\s*\\|`);
|
|
116
|
+
const rows = content.split("\n");
|
|
117
|
+
const at = rows.findIndex((l) => rowRe.test(l));
|
|
118
|
+
if (at < 0) {
|
|
119
|
+
status("FAIL", `${id} not found in ${spec.file}`);
|
|
120
|
+
return 1;
|
|
121
|
+
}
|
|
122
|
+
const [closed] = rows.splice(at, 1);
|
|
123
|
+
// An empty template row is a placeholder, not a record — dropped, not emptied.
|
|
124
|
+
content = rows.filter((l) => !/^\|(\s*\|)+\s*$/.test(l)).join("\n");
|
|
125
|
+
|
|
126
|
+
const cells = closed.split("|").slice(1, -1).map((c) => c.trim());
|
|
127
|
+
const subject = cells[spec.subjectCol] || cells[1] || "";
|
|
128
|
+
const closedRow = `| ${id} | ${subject} | ${by} |`;
|
|
129
|
+
const idx = content.search(/^##\s+(Closed|Discharged)/im);
|
|
130
|
+
if (idx < 0) {
|
|
131
|
+
content = content.trimEnd() + `\n\n## Closed\n\n| ID | Subject | Closed by |\n|---|---|---|\n${closedRow}\n`;
|
|
132
|
+
} else {
|
|
133
|
+
const lines = content.split("\n");
|
|
134
|
+
let insertAt = lines.length;
|
|
135
|
+
let inSection = false;
|
|
136
|
+
for (let i = 0; i < lines.length; i++) {
|
|
137
|
+
if (/^##\s+(Closed|Discharged)/i.test(lines[i])) inSection = true;
|
|
138
|
+
else if (inSection && /^\|/.test(lines[i])) insertAt = i + 1;
|
|
139
|
+
}
|
|
140
|
+
lines.splice(insertAt, 0, closedRow);
|
|
141
|
+
content = lines.join("\n");
|
|
142
|
+
}
|
|
143
|
+
content = content.replace(/\n{3,}/g, "\n\n");
|
|
144
|
+
writeFile(file, content);
|
|
145
|
+
heading(`${spec.label} closed`);
|
|
146
|
+
status("PASS", id, by);
|
|
147
|
+
line(color.dim(" Closure names the change that closed it (MEM-003)."));
|
|
148
|
+
line("");
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
status("FAIL", `unknown action: ${action}`);
|
|
153
|
+
return 2;
|
|
154
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { loadRules, ruleStats } from "../lib/rules.mjs";
|
|
3
|
+
import { exists, read, packageRoot } from "../lib/fs.mjs";
|
|
4
|
+
import { color, heading, line, status } from "../lib/ui.mjs";
|
|
5
|
+
import { parseFrontmatter } from "../lib/yaml.mjs";
|
|
6
|
+
|
|
7
|
+
/** Prefer the copy pinned in the project; fall back to the installed package. */
|
|
8
|
+
function rulesDir(ctx) {
|
|
9
|
+
const vendored = path.join(ctx.deviaDir, "standard", "rules");
|
|
10
|
+
return exists(vendored) ? vendored : path.join(packageRoot, "rules");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default async function rules(ctx) {
|
|
14
|
+
const { flags } = ctx;
|
|
15
|
+
|
|
16
|
+
if (flags.help) {
|
|
17
|
+
line(`
|
|
18
|
+
${color.bold("devia rules")} — the rule registry
|
|
19
|
+
|
|
20
|
+
devia rules list every active rule
|
|
21
|
+
devia rules --id SEC-001 show one rule in full
|
|
22
|
+
devia rules --domain security filter by domain
|
|
23
|
+
devia rules --priority P0 filter by priority
|
|
24
|
+
devia rules --severity "MUST NOT" filter by severity
|
|
25
|
+
devia rules --search webhook filter by text
|
|
26
|
+
devia rules --stats counts only
|
|
27
|
+
devia rules --json machine-readable
|
|
28
|
+
`.trim());
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const dir = rulesDir(ctx);
|
|
33
|
+
const { rules: all, errors } = loadRules(dir);
|
|
34
|
+
|
|
35
|
+
if (errors.length && !ctx.json) {
|
|
36
|
+
for (const e of errors) status("WARN", "registry", e);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (flags.id) {
|
|
40
|
+
const id = String(flags.id).toUpperCase();
|
|
41
|
+
const rule = all.find((r) => r.id === id);
|
|
42
|
+
if (!rule) {
|
|
43
|
+
console.error(`no such rule: ${id}`);
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
const file = path.join(dir, rule.file);
|
|
47
|
+
if (ctx.json) {
|
|
48
|
+
const { body } = parseFrontmatter(read(file) || "");
|
|
49
|
+
console.log(JSON.stringify({ ...rule, body }, null, 2));
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
line("");
|
|
53
|
+
line(read(file) || "");
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let list = all;
|
|
58
|
+
if (flags.domain) list = list.filter((r) => r.domain === String(flags.domain));
|
|
59
|
+
if (flags.priority) list = list.filter((r) => r.priority === String(flags.priority).toUpperCase());
|
|
60
|
+
if (flags.severity)
|
|
61
|
+
list = list.filter((r) => r.severity === String(flags.severity).toUpperCase());
|
|
62
|
+
if (flags.status) list = list.filter((r) => r.status === String(flags.status));
|
|
63
|
+
else list = list.filter((r) => r.status === "active");
|
|
64
|
+
if (flags.search) {
|
|
65
|
+
const q = String(flags.search).toLowerCase();
|
|
66
|
+
list = list.filter((r) =>
|
|
67
|
+
`${r.id} ${r.title} ${r.requirement}`.toLowerCase().includes(q)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (ctx.json) {
|
|
72
|
+
console.log(JSON.stringify({ count: list.length, rules: list }, null, 2));
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (flags.stats) {
|
|
77
|
+
const s = ruleStats(list);
|
|
78
|
+
heading(`Rules — ${s.total}`);
|
|
79
|
+
for (const [k, v] of Object.entries(s.priority).sort()) line(` ${k} ${v}`);
|
|
80
|
+
line("");
|
|
81
|
+
for (const [k, v] of Object.entries(s.domain).sort()) line(` ${k.padEnd(16)} ${v}`);
|
|
82
|
+
line("");
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
heading(`Rules (${list.length})`);
|
|
87
|
+
let domain = null;
|
|
88
|
+
for (const r of list) {
|
|
89
|
+
if (r.domain !== domain) {
|
|
90
|
+
domain = r.domain;
|
|
91
|
+
line(`\n ${color.bold(domain)}`);
|
|
92
|
+
}
|
|
93
|
+
const tag =
|
|
94
|
+
r.priority === "P0" ? color.red(r.priority) : r.priority === "P1" ? color.yellow(r.priority) : color.gray(r.priority);
|
|
95
|
+
line(` ${tag} ${color.bold(r.id.padEnd(9))} ${r.severity.padEnd(8)} ${r.title}`);
|
|
96
|
+
}
|
|
97
|
+
line("");
|
|
98
|
+
line(color.dim(` devia rules --id <ID> for the full text · source: ${path.relative(ctx.root, dir).split(path.sep).join("/") || dir}`));
|
|
99
|
+
line("");
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { packageRoot, exists, read, writeFile } from "../lib/fs.mjs";
|
|
3
|
+
import { color, heading, status, line } from "../lib/ui.mjs";
|
|
4
|
+
|
|
5
|
+
/** agent key -> [template file, target path relative to the repository root] */
|
|
6
|
+
export const ADAPTERS = {
|
|
7
|
+
universal: ["AGENTS.md", "AGENTS.md"],
|
|
8
|
+
claude: ["CLAUDE.md", "CLAUDE.md"],
|
|
9
|
+
cursor: ["cursor.mdc", path.join(".cursor", "rules", "devia.mdc")],
|
|
10
|
+
copilot: ["copilot-instructions.md", path.join(".github", "copilot-instructions.md")],
|
|
11
|
+
windsurf: ["windsurfrules.md", ".windsurfrules"],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const SKILL_TARGETS = {
|
|
15
|
+
cursor: path.join(".cursor", "skills", "devia", "SKILL.md"),
|
|
16
|
+
claude: path.join(".claude", "skills", "devia", "SKILL.md"),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function installAdapters(root, { force = false, agents = Object.keys(ADAPTERS) } = {}) {
|
|
20
|
+
const written = [];
|
|
21
|
+
const kept = [];
|
|
22
|
+
for (const key of agents) {
|
|
23
|
+
const entry = ADAPTERS[key];
|
|
24
|
+
if (!entry) continue;
|
|
25
|
+
const [template, target] = entry;
|
|
26
|
+
const dest = path.join(root, target);
|
|
27
|
+
if (exists(dest) && !force) {
|
|
28
|
+
kept.push(key);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const content = read(path.join(packageRoot, "templates", "agents", template));
|
|
32
|
+
if (content === null) continue;
|
|
33
|
+
writeFile(dest, content);
|
|
34
|
+
written.push(key);
|
|
35
|
+
}
|
|
36
|
+
return { written, kept };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function installSkill(root, { force = false, agents = Object.keys(SKILL_TARGETS) } = {}) {
|
|
40
|
+
const skill = read(path.join(packageRoot, "skills", "devia", "SKILL.md"));
|
|
41
|
+
const written = [];
|
|
42
|
+
const kept = [];
|
|
43
|
+
for (const key of agents) {
|
|
44
|
+
const target = SKILL_TARGETS[key];
|
|
45
|
+
if (!target || skill === null) continue;
|
|
46
|
+
const dest = path.join(root, target);
|
|
47
|
+
if (exists(dest) && !force) {
|
|
48
|
+
kept.push(key);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
writeFile(dest, skill);
|
|
52
|
+
written.push(key);
|
|
53
|
+
}
|
|
54
|
+
return { written, kept };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default async function skills(ctx) {
|
|
58
|
+
const { root, flags, args } = ctx;
|
|
59
|
+
const action = args._[1] || "install";
|
|
60
|
+
|
|
61
|
+
if (flags.help || !["install", "list"].includes(action)) {
|
|
62
|
+
line(`
|
|
63
|
+
${color.bold("devia skills")} — the same contract for every coding agent
|
|
64
|
+
|
|
65
|
+
devia skills list
|
|
66
|
+
devia skills install [--agent all|${Object.keys(ADAPTERS).join("|")}] [--force] [--skill]
|
|
67
|
+
|
|
68
|
+
--skill also install skills/devia/SKILL.md for Cursor and Claude Code
|
|
69
|
+
`.trim());
|
|
70
|
+
return flags.help ? 0 : 2;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (action === "list") {
|
|
74
|
+
heading("Adapters");
|
|
75
|
+
for (const [key, [, target]] of Object.entries(ADAPTERS)) {
|
|
76
|
+
const there = exists(path.join(root, target));
|
|
77
|
+
status(there ? "PASS" : "SKIP", key.padEnd(10), target.split(path.sep).join("/"));
|
|
78
|
+
}
|
|
79
|
+
line("");
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const wanted = flags.agent && flags.agent !== "all" ? String(flags.agent).split(",") : undefined;
|
|
84
|
+
const res = installAdapters(root, { force: Boolean(flags.force), agents: wanted });
|
|
85
|
+
|
|
86
|
+
heading("devia skills install");
|
|
87
|
+
for (const key of res.written) status("PASS", key, "written");
|
|
88
|
+
for (const key of res.kept) status("SKIP", key, "already present — use --force");
|
|
89
|
+
|
|
90
|
+
if (flags.skill) {
|
|
91
|
+
const s = installSkill(root, { force: Boolean(flags.force) });
|
|
92
|
+
for (const key of s.written) status("PASS", `${key} skill`, "written");
|
|
93
|
+
for (const key of s.kept) status("SKIP", `${key} skill`, "already present");
|
|
94
|
+
}
|
|
95
|
+
line("");
|
|
96
|
+
return 0;
|
|
97
|
+
}
|