@adula/kit 0.2.0-alpha.1

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.
Files changed (141) hide show
  1. package/LICENSE.md +10 -0
  2. package/README.md +31 -0
  3. package/build/agent/AGENTS.template.md +28 -0
  4. package/build/agent/capabilities.md +13 -0
  5. package/build/agent/skills/adula-frontend-design/SKILL.md +59 -0
  6. package/build/agent/skills/idea-review/SKILL.md +6 -0
  7. package/build/commands/capabilities.d.ts +6 -0
  8. package/build/commands/capabilities.js +9 -0
  9. package/build/commands/doctor.d.ts +9 -0
  10. package/build/commands/doctor.js +32 -0
  11. package/build/commands/gaps.d.ts +12 -0
  12. package/build/commands/gaps.js +46 -0
  13. package/build/commands/install.d.ts +9 -0
  14. package/build/commands/install.js +91 -0
  15. package/build/commands/main.d.ts +6 -0
  16. package/build/commands/main.js +27 -0
  17. package/build/commands/module_add.d.ts +8 -0
  18. package/build/commands/module_add.js +57 -0
  19. package/build/commands/module_remove.d.ts +10 -0
  20. package/build/commands/module_remove.js +94 -0
  21. package/build/commands/resource.d.ts +8 -0
  22. package/build/commands/resource.js +29 -0
  23. package/build/commands/storage_migrate.d.ts +12 -0
  24. package/build/commands/storage_migrate.js +39 -0
  25. package/build/commands/ui.d.ts +11 -0
  26. package/build/commands/ui.js +210 -0
  27. package/build/configure.d.ts +2 -0
  28. package/build/configure.js +79 -0
  29. package/build/database/migrations/1770000000000_kit_core.d.ts +5 -0
  30. package/build/database/migrations/1770000000000_kit_core.js +10 -0
  31. package/build/database/migrations/1770000000001_kit_attachments.d.ts +5 -0
  32. package/build/database/migrations/1770000000001_kit_attachments.js +10 -0
  33. package/build/database/migrations/1770000000002_kit_saved_views.d.ts +5 -0
  34. package/build/database/migrations/1770000000002_kit_saved_views.js +10 -0
  35. package/build/database/migrations/1770000000003_kit_user_invitations.d.ts +5 -0
  36. package/build/database/migrations/1770000000003_kit_user_invitations.js +18 -0
  37. package/build/index.d.ts +51 -0
  38. package/build/index.js +34 -0
  39. package/build/providers/kit_provider.d.ts +6 -0
  40. package/build/providers/kit_provider.js +10 -0
  41. package/build/src/admin/contracts.d.ts +12 -0
  42. package/build/src/admin/contracts.js +93 -0
  43. package/build/src/admin/controller.d.ts +30 -0
  44. package/build/src/admin/controller.js +116 -0
  45. package/build/src/admin/errors.d.ts +5 -0
  46. package/build/src/admin/errors.js +9 -0
  47. package/build/src/admin/presentation.d.ts +22 -0
  48. package/build/src/admin/presentation.js +1 -0
  49. package/build/src/admin/resource_service.d.ts +170 -0
  50. package/build/src/admin/resource_service.js +763 -0
  51. package/build/src/attachments/attachment_service.d.ts +71 -0
  52. package/build/src/attachments/attachment_service.js +182 -0
  53. package/build/src/attachments/doctor.d.ts +4 -0
  54. package/build/src/attachments/doctor.js +28 -0
  55. package/build/src/attachments/storage_migrate.d.ts +39 -0
  56. package/build/src/attachments/storage_migrate.js +64 -0
  57. package/build/src/auth/ability.d.ts +22 -0
  58. package/build/src/auth/ability.js +31 -0
  59. package/build/src/auth/actor_store.d.ts +14 -0
  60. package/build/src/auth/actor_store.js +65 -0
  61. package/build/src/auth/conditions.d.ts +11 -0
  62. package/build/src/auth/conditions.js +81 -0
  63. package/build/src/auth/packed_rules.d.ts +4 -0
  64. package/build/src/auth/packed_rules.js +13 -0
  65. package/build/src/auth/sql.d.ts +11 -0
  66. package/build/src/auth/sql.js +114 -0
  67. package/build/src/commands/agent_assets.d.ts +7 -0
  68. package/build/src/commands/agent_assets.js +54 -0
  69. package/build/src/commands/doctor.d.ts +11 -0
  70. package/build/src/commands/doctor.js +261 -0
  71. package/build/src/commands/generator.d.ts +1 -0
  72. package/build/src/commands/generator.js +70 -0
  73. package/build/src/commands/source_markers.d.ts +2 -0
  74. package/build/src/commands/source_markers.js +9 -0
  75. package/build/src/core/activity.d.ts +43 -0
  76. package/build/src/core/activity.js +77 -0
  77. package/build/src/core/administration_guard.d.ts +3 -0
  78. package/build/src/core/administration_guard.js +37 -0
  79. package/build/src/core/health.d.ts +42 -0
  80. package/build/src/core/health.js +44 -0
  81. package/build/src/core/mail_test.d.ts +18 -0
  82. package/build/src/core/mail_test.js +88 -0
  83. package/build/src/core/notifications.d.ts +25 -0
  84. package/build/src/core/notifications.js +65 -0
  85. package/build/src/core/org_units.d.ts +30 -0
  86. package/build/src/core/org_units.js +123 -0
  87. package/build/src/core/roles.d.ts +72 -0
  88. package/build/src/core/roles.js +319 -0
  89. package/build/src/core/saved_views.d.ts +37 -0
  90. package/build/src/core/saved_views.js +139 -0
  91. package/build/src/core/settings.d.ts +29 -0
  92. package/build/src/core/settings.js +113 -0
  93. package/build/src/core/setup.d.ts +14 -0
  94. package/build/src/core/setup.js +89 -0
  95. package/build/src/core/ui_preferences.d.ts +12 -0
  96. package/build/src/core/ui_preferences.js +30 -0
  97. package/build/src/core/user_invitations.d.ts +23 -0
  98. package/build/src/core/user_invitations.js +144 -0
  99. package/build/src/core/users.d.ts +44 -0
  100. package/build/src/core/users.js +182 -0
  101. package/build/src/database/configure.d.ts +3 -0
  102. package/build/src/database/configure.js +41 -0
  103. package/build/src/database/schema.d.ts +7 -0
  104. package/build/src/database/schema.js +248 -0
  105. package/build/src/eslint/index.d.ts +3 -0
  106. package/build/src/eslint/index.js +64 -0
  107. package/build/src/events/adonis_jobs.d.ts +9 -0
  108. package/build/src/events/adonis_jobs.js +13 -0
  109. package/build/src/events/outbox.d.ts +18 -0
  110. package/build/src/events/outbox.js +33 -0
  111. package/build/src/events/record_mutation.d.ts +11 -0
  112. package/build/src/events/record_mutation.js +45 -0
  113. package/build/src/mcp/tools.d.ts +8 -0
  114. package/build/src/mcp/tools.js +156 -0
  115. package/build/src/org/org_service.d.ts +2 -0
  116. package/build/src/org/org_service.js +16 -0
  117. package/build/src/resource/define_resource.d.ts +4 -0
  118. package/build/src/resource/define_resource.js +48 -0
  119. package/build/src/resource/registry.d.ts +9 -0
  120. package/build/src/resource/registry.js +59 -0
  121. package/build/src/resource/types.d.ts +80 -0
  122. package/build/src/resource/types.js +1 -0
  123. package/build/src/resource/values.d.ts +5 -0
  124. package/build/src/resource/values.js +87 -0
  125. package/build/src/services/backup.d.ts +14 -0
  126. package/build/src/services/backup.js +59 -0
  127. package/build/src/services/settings.d.ts +9 -0
  128. package/build/src/services/settings.js +23 -0
  129. package/build/src/version.d.ts +1 -0
  130. package/build/src/version.js +2 -0
  131. package/build/stubs/README.md +6 -0
  132. package/build/stubs/controller.stub +8 -0
  133. package/build/stubs/main.d.ts +5 -0
  134. package/build/stubs/main.js +5 -0
  135. package/build/stubs/main.ts +5 -0
  136. package/build/stubs/modules.stub +6 -0
  137. package/build/stubs/resource_contract.stub +1 -0
  138. package/build/stubs/resource_contract.txt +390 -0
  139. package/build/stubs/routes.stub +16 -0
  140. package/build/stubs/service.stub +9 -0
  141. package/package.json +104 -0
package/LICENSE.md ADDED
@@ -0,0 +1,10 @@
1
+ # The MIT License
2
+
3
+ Copyright (c) 2023
4
+ Copyright (c) 2026 adula contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
+
10
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # @adula/kit
2
+
3
+ Unreleased 0.2.0-alpha.1 resource framework for AdonisJS 7 and PostgreSQL 17. Node 24 or later is required. MIT licensed. The target is the complete approved 1.0 scope; it has not passed acceptance.
4
+
5
+ ## Install into a consumer
6
+
7
+ Follow the canonical [installation guide](https://github.com/adulash/adula-kit/blob/main/docs/installation.md). For a new application, the separate `@adula/create-app` initializer installs the AdonisJS host and application screens from an empty directory; it is available locally until publication. For an existing official AdonisJS 7 React/PostgreSQL application, install the kit/UI archives and documented UI prerequisites, configure the kit, migrate and create the intended administrator before `node ace adula:install`. The Ace command completes initialization inside that host. No educational customers/orders/tasks modules are installed by either path.
8
+
9
+ The managed rules require the bundled `.agents/skills/adula-frontend-design/SKILL.md` for UI work. It is tailored to business workflows, asks for missing company identity at project kickoff, and uses project-owned shadcn components with modal forms and record details by default. User-requested presentation exceptions remain supported. Company identity belongs in `docs/design-identity.md`, which installation never overwrites. Doctor verifies the design skill along with the idea-review skill.
10
+
11
+ ## Contracts
12
+
13
+ `defineResource` declares fields, forms, serialization, actions and scope. `ResourceRegistry` validates module ownership and dependencies. `ResourceService` applies CASL and organizational scope to JSON operations, serialization, editor options and atomic inline reconciliation. Money is a canonical string of integer minor units within PostgreSQL bigint range; calendar dates are YYYY-MM-DD; timestamps are canonical UTC ISO strings with milliseconds. Versioned updates require the current version and stale writes return 409.
14
+
15
+ Use `buildAbility(rules, registry.all())` for schema-aware money conditions. The SQL adapter rejects ordered money predicates without schemas. Browser code may import the same matcher through `@adula/kit/auth`. Organizational restrictions remain a separate AND condition.
16
+
17
+ Other exports include `ActorStore`, schema helpers, organizational moves, settings, sequences, notifications, jobs/outbox interfaces, packed rules and the generic controller factory. Commands include `adula:resource`, `adula:install`, `adula:doctor`, `adula:module:add`, `adula:module:remove`, `adula:ui add` and `adula:capabilities`.
18
+
19
+ The optional `@adula/kit/mcp` entry exports `createResourceTools`. Install @jrmc/adonis-mcp 2.0.0 and supply an authenticated actor resolver. Cookie-authenticated MCP routes must keep CSRF protection enabled. Tool inputs cannot supply identity or organizational scope.
20
+
21
+ ## Generated HTTP contracts
22
+
23
+ Generated tests call the copied `resourceContract(name, fixture)` helper. Extend the application-owned fixture whenever fields or validation change. Supply `input` and a valid `update`, plus `expected` and `updated` values after normalization. Every form field needs an input fixture; every written scalar needs a storage expectation. Use `stored`/`updatedStored` for writable fields excluded from the response, and `inline`/`updatedInline` for the complete live child rows in ID order. Dates use YYYY-MM-DD, timestamps canonical UTC strings, and money integer-minor-unit strings in expectations. Fixture callbacks receive `userId`, `orgUnitId` and a unique suffix for creating real related records.
24
+
25
+ The harness creates database-backed roles and organization memberships, checks existing-record isolation across reads and writes, central-resource visibility, serialization, mapped storage, forbidden fields, stale versions, unique constraints and soft deletion. It refuses non-test databases. Configuration preserves existing copied helpers: consumers with the old one-argument helper must review and merge the new template before generating tests that use fixtures.
26
+
27
+ Doctor measures the deployment's `storage/uploads` directory and warns above 5 decimal GB. When copied UI hashes or kit compatibility change, it inventories project pages for review. This conservative check does not prove import compatibility, completed page review or attachment restoration.
28
+
29
+ ## Current limitations
30
+
31
+ Attachment ownership, upload/download, storage migration, authentication lifecycle/rate limiting, generic resource UI and core administration have local integration consumers and tests. Real OAuth/SMTP, full Tuyau field contracts, human/performance acceptance, XState workflows, later business features, staging/offsite restoration and a genuine minor-version consumer upgrade remain open. Submission envelopes are durable records in pending_definition state, not running workflows. See [implementation status](https://github.com/adulash/adula-kit/blob/main/docs/implementation-status.md), [gaps](https://github.com/adulash/adula-kit/blob/main/KIT_GAPS.md) and [1.0 acceptance](https://github.com/adulash/adula-kit/blob/main/docs/acceptance-1.0.md). No package has been published.
@@ -0,0 +1,28 @@
1
+ <!-- adula-kit:start (managed by adula:doctor — do not edit) -->
2
+
3
+ # adula-kit rules
4
+
5
+ A plugin is just code that has not been written yet. No abstraction before the second need.
6
+
7
+ Structure
8
+
9
+ 1. One system. Every entity lives in app/modules/<module>. No separate "apps".
10
+ 2. Generate the resource scaffold with adula:resource and follow its typed definition. Reuse an existing project resource when available; never assume educational modules are installed.
11
+ 3. New entity: `node ace adula:resource <name> --module=<module>`, then fill the fields.
12
+ 4. Before creating an entity, check the registry. Same meaning exists → belongsTo. Never duplicate a table.
13
+ 5. A module writes only its own tables; dependency direction follows start/modules.ts, never backwards.
14
+ 6. Modules talk through events, never by importing each other's controllers. Heavy listeners go to jobs.
15
+ 7. Every resource declares `scoped` explicitly. Standard columns are generated; never edit them. Migrations follow expand/contract: never drop or rename a column in the same release that stops using it.
16
+ 8. Deletes are soft. Changeable lists come from lookups. Approvable documents use `submittable: true`.
17
+ 9. Notify via notify(), number via sequence, configure via settings. Nothing else.
18
+
19
+ Security 10. Every route passes the authorize middleware. Every transformer uses `serialize` (explicit pick). 11. Never write crypto, sessions, or auth flows; use kit. 2FA and impersonation changes need a human review. 12. Never delete or weaken a test to make the build pass.
20
+
21
+ UI 13. Before designing or changing any interface, read and apply `.agents/skills/adula-frontend-design/SKILL.md` (the kit's bundled frontend-design skill for business applications). At project kickoff, request the company's identity before the first design: name, logo, colors, fonts and brand guidelines when available. Reuse supplied identity, ask only for missing information, and preserve it in project-owned docs/design-identity.md. Fill the resource definition before writing a page. Override only via pages/<resource>/. 14. Compose interfaces from the project-owned shadcn/ui components in inertia/components/ui/. Use Button, Input, Select, Table, Card and the other registry primitives instead of hand-styled native controls or homemade equivalents. Semantic HTML for structure, text and form submission is allowed. Add components only via `node ace adula:ui add`, never `shadcn add`. 15. All forms (including create/edit) and row/record detail views use shadcn Dialog (modal) by default. Use ResourceSurface for resource forms/details; do not choose a standalone page, Sheet or custom overlay unless the user explicitly requests that alternative. View dialogs close on outside click; edit dialogs ask before closing by default. Honor system preferences for Gregorian, Hijri or both calendars and reduced motion. RTL by default; Arabic labels; English identifiers. Installation terminal instructions are English. Preserve explicit user exceptions and project-owned customizations.
22
+
23
+ Boundaries 16. Never modify node_modules/@adula and never use patch-package (enforced by lint). Do not re-implement a kit service under another name (checked in review). 17. A limitation is recorded in KIT_GAPS.md (template inside) and reported to the developer — never worked around. 18. Only packages already in package.json. Ask before adding one. 19. New module or workflow → run the idea-review skill first. Field changes do not need it. 20. Before finishing: `npm run typecheck && npm test && node ace adula:doctor`.
24
+ <!-- adula-kit:end -->
25
+
26
+ ## Project rules
27
+
28
+ This is a reference consumer. Additions approved by the implementation plan are authorized.
@@ -0,0 +1,13 @@
1
+ # Current capabilities
2
+
3
+ Version 0.2.0-alpha.1, unreleased. Resource definitions and registry; six flat condition operators (typed limits documented in KIT_GAPS.md); CASL evaluation with structural ltree scope; JSON/Inertia resource CRUD; field contracts; keyset pagination; sequences/lookups/settings; soft deletion; optimistic concurrency; atomic inline writes/activity/outbox; deduplicated database listeners; org moves; attachment ownership/storage migration; saved views; core administration services; resource/module commands; copied shadcn UI and installation diagnostics.
4
+
5
+ The reference consumer wires authentication/session lifecycle, admin screens, authorized attachments, optional MCP, Redis actor caching, a queue worker, one scheduler and backup/restore drills. Educational modules are isolated test fixtures, not installed application features. The agent ships business frontend-design and idea-review skills, company identity intake and modal defaults. Real SMTP receipt/recovery and isolated offsite local/source-S3 attachment recovery are documented in the source repository's confirmed-achievements ledger. OAuth is optional and externally unverified. Source-S3 recovery is integrated into the operational backup commands and verified directly; local Docker backup/recovery also passed; natural scheduled operation and staging remain pending; each consumer must verify its own configuration.
6
+
7
+ This is not the 1.0 capability catalog. Read docs/implementation-status.md before using a planned feature.
8
+
9
+ Core user invitations are available through `UserInvitations` and the `core.users/invite` permission. New standalone projects include an Arabic invitation Dialog, SMTP mail and single-use 24-hour acceptance links; recipients set their own passwords and receive no automatic roles. Configure SMTP and APP_URL, then assign business roles after acceptance. Existing consumers must merge project-owned routes/pages deliberately.
10
+
11
+ Administrative mutations protect the last active administrator and the acting administrator's own access. Repeated installation repairs missing bootstrap grants. The reference settings screen controls Gregorian, Umm al-Qura Hijri or dual-calendar presentation, edit-dialog dismissal confirmation and restrained transitions. Storage and API dates stay canonical. Permission actions require confirmation and report server outcomes.
12
+
13
+ The separate @adula/create-app package creates a complete application from an empty directory, including local service provisioning, fresh migrations, an administrator and company identity intake. Node/npm and Docker for the default service mode are prerequisites; existing PostgreSQL/Redis can be used instead. The creator and kit remain unpublished; use local archives until an authorized release exists.
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: adula-frontend-design
3
+ description: Design business application interfaces for adula-kit using project-owned shadcn/ui components, Arabic RTL layouts, practical data density, and modal forms and record details. Apply whenever creating or changing an operational page, form, table, dialog, or dashboard in a kit consumer.
4
+ ---
5
+
6
+ # Frontend design for adula-kit
7
+
8
+ This is the kit's self-contained frontend-design skill. Read it before UI work; it does not require a globally installed skill or a particular agent vendor.
9
+
10
+ ## Request company identity on day one
11
+
12
+ At project kickoff, before the first visual design, inspect the user's brief, existing assets, theme and `docs/design-identity.md`. If the company identity has not been supplied, ask for it early in one concise Arabic request. For example:
13
+
14
+ > لتطبيق هوية شركتكم من أول واجهة، زوّدني باسم الشركة وشعارها وألوانها وخطوطها المعتمدة ودليل الهوية إن وُجد. وإن لم تكن لديكم هوية جاهزة، أخبرني بذلك لنحدد اتجاهًا مناسبًا لنظام الأعمال.
15
+
16
+ Use the normal conversation when requesting logo files or a brand guide; a text-only input tool cannot receive attachments. Ask only for missing items when some identity is already known. Do not request information or approval already supplied. If the user explicitly delegates brand creation or says there is no identity, propose a restrained business direction within that authorization.
17
+
18
+ Record supplied identity in project-owned `docs/design-identity.md`: company/product names, local asset paths or supplied source links, color tokens, Arabic and Latin fonts if specified, logo usage, source of each decision and unresolved items. This is application content, not a kit-managed file; installation and upgrades must preserve it. Do not claim that a proposed or inferred choice came from the company's official guidelines.
19
+
20
+ Apply known identity from the first interface through theme tokens, typography, logo placement and naming. Adapt inaccessible color combinations through readable pairings while retaining the brand's role and noting the decision. Do not distort logos or fetch unrelated branding. If a reply or assets are pending, continue independent work; use the existing theme or a clearly provisional neutral theme for unavoidable previews. Elapsed time is not brand approval. Incorporate supplied identity once it arrives.
21
+
22
+ ## Design within the application
23
+
24
+ Understand the user's task, the important action and the information hierarchy. Inspect the existing theme and nearby screens before choosing typography, density, spacing and emphasis. Build a deliberate, cohesive interface using the application's tokens and Arabic typography. Prefer a restrained business interface with clear states and useful whitespace; avoid generic dashboard decoration and invented metrics. Honor a supplied design direction.
25
+
26
+ ## Business application priorities
27
+
28
+ Optimize for repeated daily work: finding a record, understanding its status, entering accurate data and completing an authorized action. Use familiar layouts consistently across modules. A marketing landing page, experimental typography, oversized hero section or ornamental animation is not the default for an operational screen.
29
+
30
+ - Tables: prioritize identifiers, meaningful names, status, dates and amounts. Align numeric values consistently, use tabular numerals, preserve searchable/filterable columns, and keep row actions predictable. Prefer useful density with readable spacing; do not replace comparable rows with decorative cards. Show totals or KPIs only when backed by actual data.
31
+ - Forms: group related fields, order them according to the business workflow, expose required fields and format hints, distinguish editable and read-only values, and place validation beside the affected field. Use sensible existing defaults, clear save/cancel actions and visible saving/success/conflict feedback. Keep inline document items within the parent transaction.
32
+ - Details: lead with the record identifier and current status, followed by the business facts, permitted next actions, related items and audit history when available. Do not expose hidden fields or use color as the only status indicator.
33
+ - Appearance: reuse the application's typography and design tokens, quiet surfaces, clear borders and a restrained accent for the primary action. Reserve warning/destructive colors for their actual meaning. Avoid gradients, decorative motion, arbitrary font changes and excess whitespace that slows scanning unless the user specifically requests them.
34
+ - Arabic and accessibility: use RTL logical spacing and Arabic labels, keep identifiers and numeric/date inputs legible with appropriate direction, maintain keyboard order and visible focus, and keep controls usable on mobile. Long forms scroll inside their Dialog rather than switching presentation automatically.
35
+ - Operational states: provide specific Arabic loading, empty, no-results, permission-denied, validation and recoverable-error states. Distinguish an empty dataset from a failed request; never invent sample success data in a live screen.
36
+
37
+ If a general frontend-design skill is also available, apply its craft within these business constraints. Do not follow its more experimental visual suggestions when they conflict with consistency, usability or the user's workflow.
38
+
39
+ ## Compose shadcn components
40
+
41
+ Use the copied shadcn/ui primitives in `inertia/components/ui/`: Button, Input, Textarea, Select, Checkbox, Switch, Table, Card, Dialog, Alert and related components. Compose them; do not recreate controls with styled native HTML, a custom modal overlay or an unrelated component library. Semantic elements such as main, section, headings, dl and form remain appropriate for structure and submission. Hidden inputs remain appropriate for metadata.
42
+
43
+ Install missing registry components with `node ace adula:ui add <component>`. Never run `shadcn add` directly or modify installed kit internals. Preserve application-owned component customizations; preview updates and merge deliberate changes.
44
+
45
+ ## Modal is the default
46
+
47
+ Every form and every row/record detail view opens in a shadcn Dialog unless the user explicitly requests another presentation. This includes create/edit forms and custom page overrides. Do not infer a page or Sheet exception from screen size, form length or personal design preference.
48
+
49
+ For resource routes, use `ResourcePage`, which selects the modal surface by default. For a custom form/detail override, wrap its content in `ResourceSurface` with a title, description and the list's `backHref`. It keeps direct URLs usable and returns to the list when dismissed. Use `presentation="page"` only for a user-requested page exception. Keep embedded line-item fields within their parent's form, not separate nested forms. Search and filter controls may stay in the list toolbar; their form element is submission structure, not a separate record form.
50
+
51
+ Give every Dialog a visible DialogTitle and a useful DialogDescription. Keep long content scrollable within the viewport, Arabic RTL alignment, an accessible close control, focus containment and Escape dismissal. Use shadcn components inside the modal too. Keep validation and conflict messages inside it, preserve typed values on errors, and keep unauthorized actions absent. Do not replace server authorization with UI checks.
52
+
53
+ Use `mode="view"` for details and `mode="edit"` for custom forms. Details close on outside click; editors ask for confirmation on outside click, close or Escape by default and preserve input when the user continues editing. Respect `ui.preferences` rather than rebuilding dismissal logic. Confirm permission changes and destructive actions with distinct labels, and display server rejection/success feedback. Administrative access protection must remain enforced on the server.
54
+
55
+ Honor the system calendar preference: Gregorian, Umm al-Qura Hijri, or both. Reuse FieldControl/ResourceValue and the shared formatter; never store formatted Hijri text in a date column. Both mode shows two representations of one date and allows either input calendar. Keep transitions short, preserve the workspace shell and honor reduced motion. Installation terminal instructions are English for terminal compatibility; the application remains Arabic.
56
+
57
+ ## Verify the experience
58
+
59
+ Exercise opening, filling, validation, saving, viewing details and closing. Check direct links, nested selects/popovers, keyboard focus and Escape, and a narrow mobile viewport. Test the explicit page exception when adding one. Use realistic Arabic content, inspect the rendered result, and run the project's required checks. Report which behavior was actually verified without claiming the broader visual or release acceptance gate is complete.
@@ -0,0 +1,6 @@
1
+ ---
2
+ name: idea-review
3
+ description: Translate a new module or workflow into existing kit resources and declared extension points before writing code.
4
+ ---
5
+
6
+ Read docs/decisions, the resource registry, capabilities and KIT_GAPS.md. Identify existing entities before proposing new ones. Produce a compact module blueprint with owned tables, explicit scope, dependencies, relationships, events, field visibility and acceptance tests. Classify each requirement as implemented now, a declared extension, or outside the kit. Do not design a dynamic field editor, runtime plugin loader or visual workflow editor. A user's already-approved plan authorizes its specified reference modules; do not ask to approve them again.
@@ -0,0 +1,6 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ export default class Capabilities extends BaseCommand {
3
+ static commandName: string;
4
+ static description: string;
5
+ run(): Promise<void>;
6
+ }
@@ -0,0 +1,9 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import { readFile } from 'node:fs/promises';
3
+ export default class Capabilities extends BaseCommand {
4
+ static commandName = 'adula:capabilities';
5
+ static description = 'Print implemented capabilities and current acceptance boundary';
6
+ async run() {
7
+ this.logger.log(await readFile(new URL('../agent/capabilities.md', import.meta.url), 'utf8'));
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ export default class Doctor extends BaseCommand {
3
+ static commandName: string;
4
+ static description: string;
5
+ static options: {
6
+ startApp: boolean;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,32 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { diagnose } from '../src/commands/doctor.js';
4
+ import { diagnoseAttachments } from '../src/attachments/doctor.js';
5
+ import { Settings } from '../src/services/settings.js';
6
+ import { MigrationRunner } from '@adonisjs/lucid/migration';
7
+ export default class Doctor extends BaseCommand {
8
+ static commandName = 'adula:doctor';
9
+ static description = 'Check kit ownership, installation and backup readiness';
10
+ static options = { startApp: true };
11
+ async run() {
12
+ const { default: db } = await import('@adonisjs/lucid/services/db');
13
+ const findings = await diagnose(fileURLToPath(this.app.appRoot), new Settings(db.connection().getWriteClient()), this.app.inProduction, process.env);
14
+ findings.push(await diagnoseAttachments(db.connection().getWriteClient()));
15
+ const migrations = await new MigrationRunner(db, this.app, { direction: 'up' }).getList();
16
+ const missing = migrations.filter((entry) => entry.status === 'corrupt');
17
+ const pending = migrations.filter((entry) => entry.status === 'pending');
18
+ findings.push({
19
+ check: 'database.migrations',
20
+ status: missing.length ? 'fail' : pending.length ? 'warn' : 'pass',
21
+ message: missing.length
22
+ ? `Applied migration files are missing: ${missing.map((entry) => entry.name).join(', ')}`
23
+ : pending.length
24
+ ? `${pending.length} pending migrations; run migration:run after the deployment backup`
25
+ : 'Migration files and database history agree',
26
+ });
27
+ for (const finding of findings)
28
+ this.logger.log(`${finding.status.toUpperCase()} ${finding.check}: ${finding.message}`);
29
+ if (findings.some((finding) => finding.status === 'fail'))
30
+ this.exitCode = 1;
31
+ }
32
+ }
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ /** Collects KIT_GAPS.md for reporting; nothing leaves the machine without explicit confirmation. */
3
+ export default class Gaps extends BaseCommand {
4
+ static commandName: string;
5
+ static description: string;
6
+ static options: {
7
+ startApp: boolean;
8
+ };
9
+ action: string;
10
+ yes: boolean;
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,46 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { BaseCommand, args, flags } from '@adonisjs/core/ace';
8
+ import { readFile } from 'node:fs/promises';
9
+ /** Collects KIT_GAPS.md for reporting; nothing leaves the machine without explicit confirmation. */
10
+ export default class Gaps extends BaseCommand {
11
+ static commandName = 'adula:gaps';
12
+ static description = 'Show the project gap report with private names masked before it is sent';
13
+ static options = { startApp: false };
14
+ async run() {
15
+ if (this.action !== 'report')
16
+ throw new Error('Usage: adula:gaps report [--yes]');
17
+ const path = this.app.makePath('KIT_GAPS.md');
18
+ let content;
19
+ try {
20
+ content = await readFile(path, 'utf8');
21
+ }
22
+ catch {
23
+ throw new Error('KIT_GAPS.md does not exist; adula:install creates it');
24
+ }
25
+ const masked = content
26
+ .replace(/[\w.+-]+@[\w-]+(\.[\w-]+)+/g, '<email>')
27
+ .replace(/https?:\/\/[^\s)]+/g, '<url>')
28
+ .replace(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g, '<ip>');
29
+ const gaps = masked.match(/^## GAP-\d+.*$/gm) ?? [];
30
+ this.logger.info(`${gaps.length} gap(s) recorded in KIT_GAPS.md`);
31
+ for (const title of gaps)
32
+ this.logger.log(` ${title.replace(/^## /, '')}`);
33
+ if (!this.yes) {
34
+ const confirmed = await this.prompt.confirm('Show the full masked report? Nothing is sent anywhere by this command.');
35
+ if (!confirmed)
36
+ return;
37
+ }
38
+ this.logger.log(masked);
39
+ }
40
+ }
41
+ __decorate([
42
+ args.string({ description: 'report' })
43
+ ], Gaps.prototype, "action", void 0);
44
+ __decorate([
45
+ flags.boolean({ description: 'Print without asking for confirmation' })
46
+ ], Gaps.prototype, "yes", void 0);
@@ -0,0 +1,9 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ export default class Install extends BaseCommand {
3
+ static commandName: string;
4
+ static description: string;
5
+ static options: {
6
+ startApp: boolean;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,91 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import { readFile, writeFile, access } from 'node:fs/promises';
3
+ import { Settings } from '../src/services/settings.js';
4
+ import { syncAgentAssets } from '../src/commands/agent_assets.js';
5
+ import { KIT_VERSION } from '../src/version.js';
6
+ import { fileURLToPath } from 'node:url';
7
+ export default class Install extends BaseCommand {
8
+ static commandName = 'adula:install';
9
+ static description = 'Install core organization, administrator role and managed agent instructions after migrations';
10
+ static options = { startApp: true };
11
+ async run() {
12
+ const { default: db } = await import('@adonisjs/lucid/services/db');
13
+ const knex = db.connection().getWriteClient();
14
+ const email = process.env.ADULA_ADMIN_EMAIL;
15
+ if (!email)
16
+ throw new Error('Set ADULA_ADMIN_EMAIL to an existing user email. Create the user through the official auth flow first.');
17
+ const user = await knex('users').where({ email }).first('id');
18
+ if (!user)
19
+ throw new Error('Administrator user does not exist. Create it using the official signup flow first.');
20
+ await knex.transaction(async (trx) => {
21
+ await trx.raw('SELECT pg_advisory_xact_lock(717011)');
22
+ let root = await trx('org_units').whereNull('parent_id').first();
23
+ if (!root) {
24
+ const [created] = await trx('org_units')
25
+ .insert({ name: 'الجهة', type: 'root', path: 'root' })
26
+ .returning('*');
27
+ await trx('org_units')
28
+ .where('id', created.id)
29
+ .update({ path: String(created.id) });
30
+ root = created;
31
+ }
32
+ let role = await trx('roles').where('name', 'administrator').first();
33
+ if (!role) {
34
+ const [created] = await trx('roles')
35
+ .insert({ name: 'administrator', permission_level: 1 })
36
+ .returning('*');
37
+ role = created;
38
+ }
39
+ // Repair a missing bootstrap grant even when the role already exists.
40
+ // The transaction lock makes repeated/concurrent installation idempotent.
41
+ if (!(await trx('role_rules')
42
+ .where({ role_id: role.id, subject: 'all', action: 'manage', inverted: false })
43
+ .whereNull('conditions')
44
+ .whereNull('fields')
45
+ .first()))
46
+ await trx('role_rules').insert({ role_id: role.id, subject: 'all', action: 'manage' });
47
+ if (!(await trx('user_roles')
48
+ .where({ user_id: user.id, role_id: role.id })
49
+ .whereNull('org_unit_id')
50
+ .first()))
51
+ await trx('user_roles').insert({ user_id: user.id, role_id: role.id });
52
+ await trx('user_org_units')
53
+ .insert({ user_id: user.id, org_unit_id: root.id })
54
+ .onConflict(['user_id', 'org_unit_id'])
55
+ .ignore();
56
+ await new Settings(trx).set('kit.version', KIT_VERSION);
57
+ });
58
+ for (const [name, content] of [
59
+ ['CLAUDE.md', 'Read and follow AGENTS.md.\nThe project rules are maintained there.\n'],
60
+ [
61
+ 'KIT_GAPS.md',
62
+ '# Kit gaps\n\nRecord Needed by, Tried, Blocked because, Proposed kit change, Workaround.\n',
63
+ ],
64
+ ]) {
65
+ const path = this.app.makePath(name);
66
+ try {
67
+ await access(path);
68
+ }
69
+ catch {
70
+ await writeFile(path, content, { flag: 'wx' });
71
+ }
72
+ }
73
+ await syncAgentAssets(fileURLToPath(this.app.appRoot));
74
+ const pkg = JSON.parse(await readFile(this.app.makePath('package.json'), 'utf8'));
75
+ let uiInstalled = false;
76
+ try {
77
+ await access(this.app.makePath('ui.lock.json'));
78
+ uiInstalled = true;
79
+ }
80
+ catch (error) {
81
+ if (error.code !== 'ENOENT')
82
+ throw error;
83
+ }
84
+ if (pkg.dependencies?.['@adula/ui'] && !uiInstalled) {
85
+ const ui = await this.kernel.exec('adula:ui', ['add', 'all']);
86
+ if (ui.exitCode)
87
+ throw new Error('UI installation failed; existing project customizations require review');
88
+ }
89
+ this.logger.success('Core installation complete. Run adula:doctor.');
90
+ }
91
+ }
@@ -0,0 +1,6 @@
1
+ import Doctor from './doctor.js';
2
+ import Capabilities from './capabilities.js';
3
+ export declare function getMetaData(): Promise<import("@adonisjs/core/types/ace").CommandMetaData[]>;
4
+ export declare function getCommand(meta: {
5
+ commandName: string;
6
+ }): Promise<typeof Capabilities | typeof Doctor | null>;
@@ -0,0 +1,27 @@
1
+ import Resource from './resource.js';
2
+ import Doctor from './doctor.js';
3
+ import Install from './install.js';
4
+ import Capabilities from './capabilities.js';
5
+ import ModuleAdd from './module_add.js';
6
+ import ModuleRemove from './module_remove.js';
7
+ import Ui from './ui.js';
8
+ import StorageMigrate from './storage_migrate.js';
9
+ const commands = [
10
+ Resource,
11
+ Doctor,
12
+ Install,
13
+ Capabilities,
14
+ ModuleAdd,
15
+ ModuleRemove,
16
+ Ui,
17
+ StorageMigrate,
18
+ ];
19
+ export async function getMetaData() {
20
+ return commands.map((command) => {
21
+ command.boot();
22
+ return command.serialize();
23
+ });
24
+ }
25
+ export async function getCommand(meta) {
26
+ return commands.find((command) => command.commandName === meta.commandName) ?? null;
27
+ }
@@ -0,0 +1,8 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ export default class ModuleAdd extends BaseCommand {
3
+ static commandName: string;
4
+ static description: string;
5
+ name: string;
6
+ reference: boolean;
7
+ run(): Promise<void>;
8
+ }
@@ -0,0 +1,57 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { BaseCommand, args, flags } from '@adonisjs/core/ace';
8
+ import { mkdir, readFile, writeFile, access } from 'node:fs/promises';
9
+ import { identifier } from '../src/resource/define_resource.js';
10
+ import { appendMarkedItem } from '../src/commands/source_markers.js';
11
+ export default class ModuleAdd extends BaseCommand {
12
+ static commandName = 'adula:module:add';
13
+ static description = 'Create and register an application-owned module';
14
+ async run() {
15
+ identifier(this.name);
16
+ if (this.app.inProduction)
17
+ throw new Error('Generate modules in the source checkout, not in production');
18
+ const path = this.app.makePath('app/modules', this.name);
19
+ try {
20
+ await access(path);
21
+ throw new Error(`Module directory already exists: ${this.name}`);
22
+ }
23
+ catch (error) {
24
+ if (error.code !== 'ENOENT')
25
+ throw error;
26
+ }
27
+ const indexPath = this.app.makePath('start/modules.ts');
28
+ const source = await readFile(indexPath, 'utf8');
29
+ if (!source.includes('// adula:imports'))
30
+ throw new Error('Module index registration markers are missing');
31
+ const alias = `module_${this.name}`;
32
+ const next = appendMarkedItem(source.replace('// adula:imports', `import ${alias} from '#modules/${this.name}/module'\n// adula:imports`), '/* adula:modules */', alias);
33
+ for (const directory of [
34
+ 'models',
35
+ 'resources',
36
+ 'validators',
37
+ 'migrations',
38
+ 'factories',
39
+ 'listeners',
40
+ 'jobs',
41
+ 'workflows',
42
+ 'reports',
43
+ 'pages',
44
+ 'tests',
45
+ ])
46
+ await mkdir(this.app.makePath('app/modules', this.name, directory), { recursive: true });
47
+ await writeFile(this.app.makePath('app/modules', this.name, 'module.ts'), `import type { Module } from '@adula/kit'\n// adula:imports\nexport default { name: '${this.name}', label: { ar: '${this.name}', en: '${this.name}' }, reference: ${Boolean(this.reference)}, dependsOn: [], resources: [/* adula:resources */] } satisfies Module\n`, { flag: 'wx' });
48
+ await writeFile(indexPath, next);
49
+ this.logger.success(`Created ${this.name}. Fill bilingual labels and declare dependencies before adding resources.`);
50
+ }
51
+ }
52
+ __decorate([
53
+ args.string()
54
+ ], ModuleAdd.prototype, "name", void 0);
55
+ __decorate([
56
+ flags.boolean({ description: 'Mark a disposable reference module' })
57
+ ], ModuleAdd.prototype, "reference", void 0);
@@ -0,0 +1,10 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ export default class ModuleRemove extends BaseCommand {
3
+ static commandName: string;
4
+ static description: string;
5
+ static options: {
6
+ startApp: boolean;
7
+ };
8
+ name: string;
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,94 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { BaseCommand, args } from '@adonisjs/core/ace';
8
+ import { mkdir, rename, realpath, lstat, readdir } from 'node:fs/promises';
9
+ import { dirname, join, relative, isAbsolute } from 'node:path';
10
+ import { identifier } from '../src/resource/define_resource.js';
11
+ export default class ModuleRemove extends BaseCommand {
12
+ static commandName = 'adula:module:remove';
13
+ static description = 'Unregister and archive a reference module; preserve its database tables';
14
+ static options = { startApp: true };
15
+ async run() {
16
+ identifier(this.name);
17
+ if (this.app.inProduction)
18
+ throw new Error('Remove reference modules in the source checkout, not in production');
19
+ const { modules } = await this.app.import('#start/modules');
20
+ const module = modules.find((entry) => entry.name === this.name);
21
+ if (!module?.reference)
22
+ throw new Error('Only modules explicitly marked reference: true can be removed');
23
+ const dependents = modules.filter((entry) => entry.dependsOn.includes(this.name));
24
+ if (dependents.length)
25
+ throw new Error(`Remove dependent reference modules first: ${dependents.map((entry) => entry.name).join(', ')}`);
26
+ const root = await realpath(this.app.makePath());
27
+ const path = this.app.makePath('app/modules', this.name);
28
+ const actual = await realpath(path);
29
+ const inside = relative(root, actual);
30
+ const directoryInfo = await lstat(path);
31
+ if (directoryInfo.isSymbolicLink() || inside.startsWith('..') || isAbsolute(inside))
32
+ throw new Error('Refusing to move a module outside the application');
33
+ const codemods = await this.createCodemods();
34
+ const project = await codemods.getTsMorphProject();
35
+ if (!project)
36
+ throw new Error('Module removal requires @adonisjs/assembler');
37
+ const source = project.addSourceFileAtPathIfExists(this.app.makePath('start/modules.ts'));
38
+ const imported = source.getImportDeclaration(`#modules/${this.name}/module`);
39
+ const alias = imported?.getDefaultImport()?.getText();
40
+ const declaration = source.getVariableDeclaration('modules')?.getInitializer();
41
+ if (!alias || !declaration || !('getElements' in declaration))
42
+ throw new Error('Expected a literal module registry; no files were changed');
43
+ const array = declaration;
44
+ const index = array.getElements().findIndex((element) => element.getText() === alias);
45
+ if (index < 0)
46
+ throw new Error('Module import is not a literal registry item; no files were changed');
47
+ array.removeElement(index);
48
+ imported.remove();
49
+ const archive = this.app.tmpPath('adula-removed', `${this.name}-${Date.now()}`);
50
+ await mkdir(dirname(archive), { recursive: true });
51
+ const archiveParent = relative(root, await realpath(dirname(archive)));
52
+ if (archiveParent.startsWith('..') || isAbsolute(archiveParent))
53
+ throw new Error('Archive must stay inside the application');
54
+ const moves = [];
55
+ const move = async (from, to) => {
56
+ await rename(from, to);
57
+ moves.push([from, to]);
58
+ };
59
+ // No delete and no database mutation. Archived source receives .bak suffixes so it is not compiled.
60
+ const preserve = async (directory) => {
61
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
62
+ const file = join(directory, entry.name);
63
+ if (entry.isDirectory())
64
+ await preserve(file);
65
+ else if (entry.isFile() && /\.(ts|tsx|js|jsx)$/.test(entry.name))
66
+ await move(file, `${file}.bak`);
67
+ }
68
+ };
69
+ try {
70
+ await move(path, archive);
71
+ await preserve(archive);
72
+ for (const resource of module.resources) {
73
+ const testPath = this.app.makePath('tests/functional', `${identifier(resource.name)}.spec.ts`);
74
+ try {
75
+ await move(testPath, join(archive, `${resource.name}.spec.ts.bak`));
76
+ }
77
+ catch (error) {
78
+ if (error.code !== 'ENOENT')
79
+ throw error;
80
+ }
81
+ }
82
+ await source.save();
83
+ }
84
+ catch (error) {
85
+ for (const [from, to] of moves.reverse())
86
+ await rename(to, from);
87
+ throw error;
88
+ }
89
+ this.logger.success(`Unregistered ${this.name}; source archived at ${archive}. Database tables and migration history were preserved.`);
90
+ }
91
+ }
92
+ __decorate([
93
+ args.string()
94
+ ], ModuleRemove.prototype, "name", void 0);
@@ -0,0 +1,8 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ export default class Resource extends BaseCommand {
3
+ static commandName: string;
4
+ static description: string;
5
+ name: string;
6
+ module: string;
7
+ run(): Promise<void>;
8
+ }