@openora/create 0.4.1-canary.108 → 0.4.1-canary.110

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 (58) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/generated/core-version.d.ts +1 -1
  3. package/dist/generated/core-version.js +1 -1
  4. package/dist/index.js +16 -1
  5. package/dist/index.js.map +1 -1
  6. package/package.json +2 -2
  7. package/template/README.md.tpl +6 -6
  8. package/template/__dot__gitignore +3 -0
  9. package/template/__dot__rulesync/commands/check.md +11 -10
  10. package/template/__dot__rulesync/commands/doctor.md.tpl +17 -0
  11. package/template/__dot__rulesync/commands/scaffold-module.md +2 -1
  12. package/template/__dot__rulesync/commands/scaffold-plugin.md +1 -0
  13. package/template/__dot__rulesync/commands/scaffold-route.md +2 -1
  14. package/template/__dot__rulesync/commands/start.md +1 -1
  15. package/template/__dot__rulesync/hooks/_shared.mjs +5 -0
  16. package/template/__dot__rulesync/hooks/guard-generated.mjs +9 -2
  17. package/template/__dot__rulesync/hooks/guard-subagent.mjs +17 -5
  18. package/template/__dot__rulesync/hooks/post-edit.mjs.tpl +92 -0
  19. package/template/__dot__rulesync/hooks.json +22 -4
  20. package/template/__dot__rulesync/rules/conventions.md +29 -8
  21. package/template/__dot__rulesync/rules/db-conventions.md +17 -0
  22. package/template/__dot__rulesync/rules/e2e-conventions.md +2 -0
  23. package/template/__dot__rulesync/rules/frontend-conventions.md.tpl +35 -0
  24. package/template/__dot__rulesync/rules/oss-boundaries.md.tpl +60 -0
  25. package/template/__dot__rulesync/rules/overview.md +8 -0
  26. package/template/__dot__rulesync/skills/add-feature/SKILL.md.tpl +97 -0
  27. package/template/__dot__rulesync/skills/add-feature/{handoff.md → handoff.md.tpl} +9 -19
  28. package/template/__dot__rulesync/skills/create-plugin/{SKILL.md → SKILL.md.tpl} +12 -23
  29. package/template/__dot__rulesync/skills/create-pr/SKILL.md.tpl +32 -0
  30. package/template/__dot__rulesync/skills/create-task/{SKILL.md → SKILL.md.tpl} +14 -22
  31. package/template/__dot__rulesync/skills/create-ui-module/{SKILL.md → SKILL.md.tpl} +12 -10
  32. package/template/__dot__rulesync/skills/enhance-prompt/{SKILL.md → SKILL.md.tpl} +4 -4
  33. package/template/__dot__rulesync/skills/review/SKILL.md.tpl +165 -0
  34. package/template/__dot__rulesync/subagents/builder.md.tpl +90 -0
  35. package/template/__dot__rulesync/subagents/debugger.md.tpl +86 -0
  36. package/template/__dot__rulesync/subagents/deployer.md.tpl +54 -0
  37. package/template/__dot__rulesync/subagents/expert.md +28 -34
  38. package/template/__dot__rulesync/subagents/qa.md.tpl +61 -0
  39. package/template/__dot__rulesync/subagents/{quality-reviewer.md → quality-reviewer.md.tpl} +15 -3
  40. package/template/__dot__rulesync/subagents/{security-reviewer.md → security-reviewer.md.tpl} +7 -1
  41. package/template/__dot__rulesync/sync.json.tpl +21 -0
  42. package/template/docs/agents/forge.md.tpl +48 -0
  43. package/template/docs/agents/issue-tracker.md.tpl +41 -0
  44. package/template/docs/standards/database.md +1 -1
  45. package/template/docs/standards/enforcement.md +2 -2
  46. package/template/package.json.tpl +5 -1
  47. package/template/tools/sync-agents.mjs +162 -0
  48. package/template/__dot__rulesync/commands/doctor.md +0 -16
  49. package/template/__dot__rulesync/hooks/post-edit.mjs +0 -57
  50. package/template/__dot__rulesync/rules/oss-boundaries.md +0 -31
  51. package/template/__dot__rulesync/skills/add-feature/SKILL.md +0 -112
  52. package/template/__dot__rulesync/skills/create-pr/SKILL.md +0 -55
  53. package/template/__dot__rulesync/skills/review/SKILL.md +0 -117
  54. package/template/__dot__rulesync/subagents/builder.md +0 -93
  55. package/template/__dot__rulesync/subagents/debugger.md +0 -82
  56. package/template/__dot__rulesync/subagents/deployer.md +0 -66
  57. package/template/__dot__rulesync/subagents/qa.md +0 -88
  58. package/template/docs/agents/issue-tracker.md +0 -36
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  // PreToolUse(Task) guard: keep the orchestrator from spawning a GENERIC subagent
3
3
  // (general-purpose / claude) for work a roster agent is purpose-built for. The
4
- // Agent roster in AGENTS.md is prose the model sometimes skips out of habit; this
5
- // is the deterministic backstop.
4
+ // Agent roster in the workflow rule is prose the model sometimes skips out of
5
+ // habit; this is the deterministic backstop.
6
6
  //
7
7
  // Conservative by design: it acts ONLY when (a) the chosen subagent is generic AND
8
8
  // (b) the task text contains a HIGH-SIGNAL phrase that maps unambiguously to one
@@ -16,7 +16,9 @@ const ti = payload.tool_input ?? {};
16
16
  const sub = String(ti.subagent_type ?? '').toLowerCase();
17
17
 
18
18
  const GENERIC = new Set(['general-purpose', 'claude', '']);
19
- if (!GENERIC.has(sub)) process.exit(0);
19
+ if (!GENERIC.has(sub)) {
20
+ process.exit(0);
21
+ }
20
22
 
21
23
  const text = `${ti.description ?? ''}\n${ti.prompt ?? ''}`.toLowerCase();
22
24
 
@@ -40,14 +42,24 @@ const ROUTES = [
40
42
  agent: 'deployer',
41
43
  re: /\b(dockerfile|containerize|deploy pipeline|deploy to (ecs|kubernetes|fly|railway|render)|ci\/cd deploy|helm chart)\b/,
42
44
  },
45
+ {
46
+ agent: 'quality-reviewer',
47
+ re: /\b(quality review|code quality|over-engineer(ed|ing)?|simplification review|duplication review)\b|\breview (the )?(mr|pr|diff|changed files)\b/,
48
+ },
49
+ {
50
+ agent: 'security-reviewer',
51
+ re: /\bsecurity (review|audit)\b|\baudit\b[^.]*\bvulnerabilit|\b(authz|owasp)\b/,
52
+ },
43
53
  ];
44
54
 
45
55
  const hit = ROUTES.find((r) => r.re.test(text));
46
- if (!hit) process.exit(0);
56
+ if (!hit) {
57
+ process.exit(0);
58
+ }
47
59
 
48
60
  process.stderr.write(
49
61
  `Use the \`${hit.agent}\` subagent for this task, not \`${sub || 'general-purpose'}\`. ` +
50
- `It is pre-scoped for this work (tools + model + brief) - see the Agent roster in AGENTS.md. ` +
62
+ `It is pre-scoped for this work (tools + model + brief) - see the Agents list in the workflow rule. ` +
51
63
  `Re-issue the Task with subagent_type: "${hit.agent}". ` +
52
64
  `If it genuinely does not fit ${hit.agent}, rephrase the description to say why.\n`,
53
65
  );
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ // PostToolUse hook (Claude / Copilot CLI / Codex CLI / Gemini CLI).
3
+ // Lint-fixes the edited file, then typechecks the app that owns it and reports
4
+ // (exit 2) only if the error is in the file just edited. Output is capped so a
5
+ // failure can't balloon the model context. Fail-open on anything unexpected.
6
+
7
+ import { execSync } from 'node:child_process';
8
+ import { readFileSync } from 'node:fs';
9
+ import { join, isAbsolute, relative } from 'node:path';
10
+ import { extractFilePath, readPayload } from './_shared.mjs';
11
+
12
+ const CAP_LINES = 40;
13
+ const CAP_CHARS = 2000;
14
+
15
+ const filePath = extractFilePath(readPayload());
16
+ if (!filePath) {
17
+ process.exit(0);
18
+ }
19
+ if (!/\.(ts|tsx)$/.test(filePath) || filePath.endsWith('.d.ts')) {
20
+ process.exit(0);
21
+ }
22
+ if (filePath.includes('/templates/') || filePath.includes('/generated/')) {
23
+ process.exit(0);
24
+ }
25
+
26
+ // Lint-fix (best effort - never block on the linter).
27
+ try {
28
+ execSync(`pnpm exec oxlint --fix "${filePath}"`, { stdio: 'pipe' });
29
+ } catch {
30
+ /* oxlint unavailable or errored - fall through to the re-lint / typecheck */
31
+ }
32
+
33
+ // Re-lint WITHOUT --fix to surface UNFIXABLE errors the agent must resolve by
34
+ // hand - import/no-cycle and the no-restricted-imports boundary rules (deep @openora
35
+ // or {{scope}}/ui imports, app-to-app imports). oxlint exits non-zero on errors
36
+ // only (warnings don't fail), so this blocks on a real boundary/cycle violation
37
+ // in the edited file and feeds the message back. Fail-open on anything else.
38
+ try {
39
+ execSync(`pnpm exec oxlint "${filePath}"`, { stdio: 'pipe' });
40
+ } catch (e) {
41
+ const out = (e.stdout?.toString() ?? '') + (e.stderr?.toString() ?? '');
42
+ if (/\berror\b/.test(out)) {
43
+ process.stderr.write(
44
+ `Boundary/lint error in ${filePath} - fix before continuing:\n${cap(out)}`,
45
+ );
46
+ process.exit(2);
47
+ }
48
+ }
49
+
50
+ // Resolve the owning workspace app (apps/<x> or packages/<x>).
51
+ function packageNameFor(fp) {
52
+ const m = fp.match(/(.*?\/(?:apps|packages)\/[^/]+)\//);
53
+ if (!m) {
54
+ return null;
55
+ }
56
+ try {
57
+ const pkg = JSON.parse(readFileSync(join(m[1], 'package.json'), 'utf8'));
58
+ if (!pkg.name || !pkg.scripts?.['check:types']) {
59
+ return null;
60
+ }
61
+ return pkg.name;
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+
67
+ const abs = isAbsolute(filePath) ? filePath : join(process.cwd(), filePath);
68
+ const owner = packageNameFor(abs);
69
+ if (!owner) {
70
+ process.exit(0);
71
+ }
72
+
73
+ function cap(text) {
74
+ const out = text.split('\n').slice(0, CAP_LINES).join('\n').slice(0, CAP_CHARS);
75
+ return out.length < text.length ? `${out}\n... (truncated)` : out;
76
+ }
77
+
78
+ try {
79
+ execSync(`pnpm --filter "${owner}" check:types`, { stdio: 'pipe' });
80
+ process.exit(0);
81
+ } catch (e) {
82
+ const output = (e.stdout?.toString() ?? '') + (e.stderr?.toString() ?? '');
83
+ const basename =
84
+ (isAbsolute(filePath) ? relative(process.cwd(), filePath) : filePath).split('/').pop() ?? '';
85
+ if (basename && output.includes(basename)) {
86
+ process.stderr.write(
87
+ `Typecheck failed for ${owner} after editing ${basename}:\n${cap(output)}`,
88
+ );
89
+ process.exit(2);
90
+ }
91
+ process.exit(0);
92
+ }
@@ -2,10 +2,28 @@
2
2
  "version": 1,
3
3
  "hooks": {
4
4
  "preToolUse": [
5
- { "type": "command", "command": "node .rulesync/hooks/guard-core.mjs" },
6
- { "type": "command", "command": "node .rulesync/hooks/guard-generated.mjs" },
7
- { "type": "command", "matcher": "Task", "command": "node .rulesync/hooks/guard-subagent.mjs" }
5
+ {
6
+ "type": "command",
7
+ "matcher": "Bash|Edit|Write|MultiEdit|NotebookEdit",
8
+ "command": "node \"${CLAUDE_PROJECT_DIR:-.}/.rulesync/hooks/guard-core.mjs\""
9
+ },
10
+ {
11
+ "type": "command",
12
+ "matcher": "Bash|Edit|Write|MultiEdit|NotebookEdit",
13
+ "command": "node \"${CLAUDE_PROJECT_DIR:-.}/.rulesync/hooks/guard-generated.mjs\""
14
+ },
15
+ {
16
+ "type": "command",
17
+ "matcher": "Task|Agent",
18
+ "command": "node \"${CLAUDE_PROJECT_DIR:-.}/.rulesync/hooks/guard-subagent.mjs\""
19
+ }
8
20
  ],
9
- "postToolUse": [{ "type": "command", "command": "node .rulesync/hooks/post-edit.mjs" }]
21
+ "postToolUse": [
22
+ {
23
+ "type": "command",
24
+ "matcher": "Edit|Write|MultiEdit|NotebookEdit",
25
+ "command": "node \"${CLAUDE_PROJECT_DIR:-.}/.rulesync/hooks/post-edit.mjs\""
26
+ }
27
+ ]
10
28
  }
11
29
  }
@@ -12,7 +12,7 @@ description: Engineering code conventions (TS, DB, frontend, testing, git) - the
12
12
  The always-on core of the code standard: what you must obey while typing. Detail, examples, and
13
13
  rationale live in `docs/standards/` - read the one file that matches the change instead of
14
14
  carrying all of it. The enforced import graph lives in `oss-boundaries`; Playwright rules in
15
- `e2e-conventions`.
15
+ `e2e-conventions`; the always-on UI and SQL cores in `frontend-conventions` and `db-conventions`.
16
16
 
17
17
  | Change you are making | Read first |
18
18
  | ------------------------------------- | ------------------------------------ |
@@ -44,7 +44,9 @@ above) if this repo has no UI apps.
44
44
  ## Never (lint-enforced unless noted)
45
45
 
46
46
  - `any` outside tests, `!` non-null assertions, `as` casts to silence the compiler (`as const` is
47
- fine).
47
+ fine). Two sanctioned casts: test doubles through one `mock<T>()` helper (never inline in a
48
+ test), and a third-party inference boundary a library gives no honest way to satisfy (one cast,
49
+ one-line `// Library boundary:` note).
48
50
  - `interface`, TS `enum`, decorators, inheritance for reuse, default exports (exceptions:
49
51
  `*.config.*`, `plugin.ts`, Next.js App Router files).
50
52
  - Hand-written duplicates of an inferrable type, re-inferring an imported schema, re-typing
@@ -56,6 +58,13 @@ above) if this repo has no UI apps.
56
58
  internals, import cycles, deep `dist/`/`src/` paths into another package.
57
59
  - SQL anti-patterns (bare `timestamp()`, CamelCase identifiers, hand-edited migrations) - detail
58
60
  in `docs/standards/database.md`.
61
+ - Silent catches - log with context and rethrow. `ORPCError.message` rendered to a player - UI copy
62
+ keys off `.code` + typed `.data` through `t()`.
63
+ - Comments, unless one states a fact the code cannot contain (an external system's behaviour, a
64
+ spec constraint); a reason goes in the commit or PR, never inline. Never in tests. Detail:
65
+ `docs/standards/comments.md`.
66
+ - An in-process test that mocks the database, a repository, or a sibling service - found one in
67
+ the diff, replace it with the API E2E, do not add to it.
59
68
 
60
69
  ## Always
61
70
 
@@ -63,7 +72,8 @@ above) if this repo has no UI apps.
63
72
  (`wallet.service.ts`, never `helpers.ts`); types `PascalCase`; values/functions `camelCase`;
64
73
  true global constants `SCREAMING_SNAKE_CASE`; Zod schemas `<Name>Schema` with inferred type
65
74
  `<Name>`; booleans read as predicates (`isActive`, `canEdit`); money is a decimal string plus a
66
- `currency` field alongside it, never `amountCents`.
75
+ `currency` field alongside it, never `amountCents`; IO functions are verbs (`fetchInvoice`); names
76
+ carry units and intent (`delayMs`, `maxRetries`).
67
77
  - **One source of truth per shape** - infer, never hand-write: `z.infer<typeof XSchema>`,
68
78
  `typeof x.$inferSelect`. Full detail: `docs/standards/types.md`.
69
79
  - **Literal config arrays/objects (option lists, key sets) use `as const`**, not an explicit union
@@ -72,8 +82,9 @@ above) if this repo has no UI apps.
72
82
  the type after.
73
83
  - **Entity ids typed through their owning type** (`playerId: Player['id']`), never a bare
74
84
  `string`.
75
- - **Guard clauses first, main path last; more than 3 params -> one named object.** Detail:
76
- `docs/standards/functions.md`.
85
+ - **Guard clauses first, main path last; more than 3 params -> one named object** (a leading
86
+ `tx` handle may stay positional). Always brace control statements, even one-liners (lint:
87
+ `curly`). Detail: `docs/standards/functions.md`.
77
88
  - **Construct objects by spread + override**, never a hand-copied field list.
78
89
  - **Side effects at the edges**; money paths are transactional AND idempotent (a DB guard inside
79
90
  the transaction, not just an idempotency key). Detail: `docs/standards/errors.md`.
@@ -85,6 +96,16 @@ above) if this repo has no UI apps.
85
96
  route, an overlay, a vendor adapter, anything with SQL -> API E2E against real Postgres with the
86
97
  vendor stubbed at its HTTP boundary; a pure function -> a co-located unit test in `__tests__/`.
87
98
  Never fake a query builder and never let a spy assertion be the point of a test; always cover
88
- authz negatives. Detail: `docs/standards/testing.md`.
89
- - **Green before review:** `pnpm verify` passes. Conventional commits, lowercase subject, one PR
90
- per concern. Never push without explicit confirmation. Detail: `docs/standards/git-delivery.md`.
99
+ authz negatives. A new or changed route ships one API E2E spec
100
+ (`apps/e2e/tests/api/<domain>/<scenario>.spec.ts`): happy path plus one hostile path
101
+ (unauthorized, wrong owner, repeated call on a money path) - it is the acceptance artifact the
102
+ review's request trace reads as proof. Detail: `docs/standards/testing.md`.
103
+ - **Green before review:** `/check` (typecheck + lint + unit tests) while iterating, `pnpm verify`
104
+ (adds format, boundaries, build) before the PR. Conventional commits, lowercase subject (PR
105
+ title too - a squash merge turns it into the commit), one PR per concern. Never push without
106
+ explicit confirmation. The description carries what / why / acceptance criteria / bare ticket
107
+ key - no test plan or CI checklist the pipeline already shows, no URLs, hostnames, secrets, or
108
+ PII. Detail: `docs/standards/git-delivery.md`.
109
+ - **Fix the import, never work around a lint or boundary violation.** Agent rules are generated
110
+ from `.rulesync/` via `pnpm gen:agents` - never hand-edit a generated file. Detail:
111
+ `docs/standards/enforcement.md`.
@@ -0,0 +1,17 @@
1
+ ---
2
+ root: false
3
+ targets:
4
+ - '*'
5
+ globs:
6
+ - 'apps/api/**'
7
+ description: SQL / Drizzle conventions for tables an overlay or local add-on owns - the always-on core; full detail in docs/standards/database.md.
8
+ ---
9
+
10
+ # Database conventions (SQL / Drizzle)
11
+
12
+ Applies to every table an overlay or local add-on owns (`apps/api/src/extensions/<name>/src/schema/`). Platform-domain tables live in `@openora/*` core - never edit those. Identifiers, timestamps, keys, indexes, N+1, idempotency and migrations are specified in `docs/standards/database.md` - read it before touching a schema, query, migration, or seed. The rules below are the ones that file does not state.
13
+
14
+ - Money: exact decimal, never float, never a scaled integer. Every money column is `decimal()` (Postgres `NUMERIC`) - never `real`/`float`, never an `integer` "cents" column. Pair it with a `currency` column; on the wire use `MoneyAmountSchema` (decimal string) + `currency`, never `z.number()`. Balance math runs in SQL, never JS float arithmetic.
15
+ - `pgEnum` derives from a values + schema + type triple declared once (never an inline value array): `X_STATUSES = [...] as const` -> `XStatusSchema = z.enum(X_STATUSES)` -> `pgEnum('x_status', X_STATUSES)`.
16
+ - Bound the fan-out: never `Promise.all(rows.map(fn))` when `rows` is a query result and `fn` calls out per row (a vendor adapter, another service) - it opens unbounded concurrent connections/requests and starves everything else at scale. Use `mapConcurrent(items, limit, fn)` (`@openora/core/server`).
17
+ - Audit every mutation: each state-changing action emits a domain event the `audit` add-on subscribes to, or resolves `AUDIT_WRITER` and calls `record(...)`. A mutation with no audit trail is not done.
@@ -9,6 +9,8 @@ description: Playwright E2E conventions - specs run against the real stack; mock
9
9
 
10
10
  # E2E conventions (`apps/e2e`)
11
11
 
12
+ Applies once this repo has an `apps/e2e` Playwright suite; a repo without one is not covered by this rule, and adding the suite is what turns it on.
13
+
12
14
  Two kinds of spec live here:
13
15
 
14
16
  - **Browser specs** (`tests/<app>/**`) - a player or admin journey through the UI.
@@ -0,0 +1,35 @@
1
+ ---
2
+ root: false
3
+ targets:
4
+ - '*'
5
+ globs:
6
+ - 'apps/web/**'
7
+ - 'apps/backoffice/**'
8
+ - 'packages/ui/**'
9
+ description: React/UI conventions (component library, theming, i18n, permission gating, modular architecture) for apps/web, apps/backoffice, packages/ui - applies once those apps exist; full detail in docs/standards/frontend.md.
10
+ ---
11
+
12
+ # Frontend conventions
13
+
14
+ Applies once this repo has UI apps (`apps/web`, `apps/backoffice`, `packages/ui`); an api-only repo has none of those paths and this rule is inert there.
15
+
16
+ UI-specific rules for `apps/web`, `apps/backoffice`, and `packages/ui`. Stack-agnostic rules (naming, types, functions, comments, errors, testing, git) live in `conventions`. Styling, i18n, and the modular architecture are specified in `docs/standards/frontend.md` - read it before touching a component, page, or module. The rules below are the ones that file does not state.
17
+
18
+ ## Component library
19
+
20
+ - `{{scope}}/ui` is the component library: compose from its primitives (`Button`, `Dialog`, `Select`, `Switch`, `Tabs`, `Badge`, ...) plus utility classes on the theme tokens. Don't hand-roll what a primitive already provides; a genuinely missing one is added to `packages/ui/src/primitives/`.
21
+ - Theme tokens and CSS variables are declared once in `packages/ui/themes.css`; `cn()` and `registerTranslations()` come from the `{{scope}}/ui` barrel - never deep-import.
22
+ - `{{scope}}/ui` is consumed pre-built, so the React Compiler exception in `docs/standards/frontend.md` applies to it.
23
+
24
+ ## Permission-gated UI
25
+
26
+ A page, nav item, tab, panel, or write action gated on a permission must check the exact resource/level the backend route actually asserts (`adminGuard.assert(resource, action)`) - never a looser, unrelated, or merely-plausible resource, and never a coarse "is admin" shortcut. Confirm the check against the route/service being called, not against what reads correctly at a glance.
27
+
28
+ - Gate at every layer the feature touches, and keep them in agreement: the nav entry, the route guard, the data-fetching hook's `enabled`, and the rendered branch. A mismatch between any two of these reopens the exact bug class this guards against - a request that fires (and 403s) before the UI hides itself, a control shown that fails on click, or a section rendering an empty/failed state instead of disappearing.
29
+ - Thread the permission into the query hook itself (`enabled: Boolean(id) && useHasPermission(resource, level)`), not only into the component that renders it - a hook with no grant check still fires for any caller that forgets the wrapper, including ones added later.
30
+ - A missing or denied permission hides the section outright - never a failed-to-load screen, an empty state standing in for "no access," or a disabled-but-visible control. See `conventions` > Testing: cover the authz-negative for every new gate as part of the same change.
31
+
32
+ ## Modular architecture
33
+
34
+ - Outer composition code (`src/app/`, `src/routes/`) imports a module only as `@/modules/<name>`; files inside a module use relative paths to siblings.
35
+ - Cross-module communication is query invalidation, never a direct import.
@@ -0,0 +1,60 @@
1
+ ---
2
+ root: false
3
+ targets:
4
+ - '*'
5
+ globs:
6
+ - '**/*'
7
+ description: OSS core is read-only; enforced import/module boundaries.
8
+ ---
9
+
10
+ # OSS core + import boundaries
11
+
12
+ ## Never modify OSS core
13
+
14
+ `@openora/*` is a third-party dependency - read it for reference, never write to it.
15
+
16
+ - Do NOT edit `node_modules/**` or the linked OSS checkout (`{{ossDir}}`). Those paths are write-denied in `.claude/settings.json`; don't route around it with `sed`, redirection, or scripts. A patched dependency is lost on reinstall and diverges from the published package.
17
+ - Extend from the OUTSIDE only: overlay plugins, adapter rebindings, UI plugins, config.
18
+ - If something can only be fixed in core, STOP and report it upstream (problem, expected behavior, likely location).
19
+
20
+ ## Import boundaries (enforced)
21
+
22
+ Enforced by `pnpm check:lint` (oxlint, per-edit), the pre-commit hook, CI, and the agent PostToolUse hook - plus `pnpm check:boundaries` (dependency-cruiser, one graph per app/package via turbo) and `pnpm check:shape` in repos that ship them. Fix the import, never work around a violation.
23
+
24
+ ### Across packages
25
+
26
+ - No deep OSS imports: `@openora/*/src/*` or `/dist/*` - import only the package entrypoint (oxlint owns this one; every published entrypoint resolves into `dist/`, so the boundary graph cannot tell the two apart).
27
+ - No deep `{{scope}}/ui` imports - only the barrel.
28
+ - No app-to-app imports (`apps/web` <-> `apps/backoffice` <-> `apps/api`) - extract shared code to `packages/*`. Never reach across with a relative `../../apps/` path either.
29
+ - No deep workspace-package imports - only the index entrypoint.
30
+ - No import cycles.
31
+ - A shared package (`packages/*`) must not import from an app.
32
+ - `{{scope}}/ui` is renderer- and router-agnostic: no `next`, no router package. Take the capability through an injected adapter (see `navigation.tsx`).
33
+ - No Node builtins in browser code (the Next instrumentation/proxy entries and `src/lib/api-server.*` are the declared exceptions).
34
+
35
+ ### Inside an app (ADR-0001)
36
+
37
+ - No cross-module imports; a module is reached only through its barrel `index.ts`.
38
+ - Layering inside a module: `pages/` is the leaf - `components/`, `hooks/`, `utils/` must not import it; `utils/` must not import `components/`, `hooks/` or `pages/`.
39
+ - A file inside a module must not import its own barrel.
40
+ - The shared kernel (`src/lib`, `src/hooks`, `src/utils`, `src/components`) must not import a feature module - dependencies point inwards.
41
+ - Routes are leaves: nothing may import `src/app/**` (web) or `src/routes/**` / `routeTree.gen.ts` (backoffice, except `main.tsx`).
42
+ - Transport clients (`@orpc/client`, `@orpc/openapi-client`, `@orpc/tanstack-query`) are constructed in `src/lib/` only.
43
+ - A `'use client'` file must not import server-only code (`next/headers`, `src/lib/api-server.*`, `*.server.ts`).
44
+
45
+ ### Dependency manifest hygiene
46
+
47
+ - Every import resolves; every npm package used is declared in that package's own `package.json`; no package sits in two dependency sections (a library's devDependency + peerDependency pair is the exception).
48
+ - Production code imports no devDependency and no test/mock/fixture file.
49
+
50
+ ### Structure checks (`pnpm check:shape`, in repos that ship it)
51
+
52
+ Runs the checks a dependency graph cannot make, because they are about files that do not exist or edges that do not exist:
53
+
54
+ - Every module has `index.ts` and `AGENTS.md`.
55
+ - The barrel re-exports only; the single side effect it may carry is `import './locales'`.
56
+ - Every file under a module's `pages|components|hooks|utils` is reachable from the barrel, and every `packages/ui/src` file from the `{{scope}}/ui` barrel - otherwise it is dead code.
57
+
58
+ Each app/package owns its own `.dependency-cruiser.cjs` (its own tsconfig for `@/*` alias resolution) built on the shared rule/option helpers in `.dependency-cruiser.shared.cjs`. Where the repo ships `pnpm gen:boundaries-graph`, it renders each package's graph (needs Graphviz).
59
+
60
+ The shared options keep npm packages in the graph on purpose - a dependency-type rule can only judge a package that is a node - so build output is excluded per workspace path, never as a bare `/dist/`.
@@ -23,6 +23,14 @@ Sibling rules (load on demand; don't reopen settled questions):
23
23
  - `conventions` - the always-on code standard (naming, types, functions, package structure, errors, testing, git, frontend, DB), with a table routing each kind of change to its deep-dive file in `docs/standards/`.
24
24
  - `oss-boundaries` - OSS core is read-only; enforced import/module boundaries.
25
25
  - `e2e-conventions` - dual-mode Playwright specs, fixtures, mocks, page objects.
26
+ - `db-conventions` - SQL / Drizzle rules for the tables an overlay owns.
27
+ - `frontend-conventions` - React/UI rules for the operator's apps and shared UI package.
28
+
29
+ ## Agent files: generated from the template, or owned here
30
+
31
+ Most of `.rulesync/` (skills, subagents, commands, hooks, the shared rules) and `docs/standards/` are generated: `pnpm install` renders them from `@openora/create`'s consumer template at the pinned `@openora/*` version, using the variables in `.rulesync/sync.json`. They are gitignored via the managed `synced-agents` block in `.gitignore`, so they never appear in a diff and cannot drift - editing one locally is pointless, the next install overwrites it. Change it upstream in the template instead. This repo owns and tracks the rules and skills the template does not ship, plus the `consumerOwned` overrides in `.rulesync/sync.json`; operator-specific facts belong there or in `sync.json` vars.
32
+
33
+ Most of `.rulesync/` (skills, subagents, commands, hooks, the shared rules) and `docs/standards/` are template-owned: rendered from `@openora/create`'s consumer template at the pinned `@openora/*` version with the variables in `.rulesync/sync.json`. `pnpm check:agents` fails on any drift, so never edit a synced file here - change it upstream in openora's `tools/templates/consumer/`, take the next canary, run `pnpm sync:agents`. Files this repo owns are listed under `consumerOwned` in `.rulesync/sync.json` plus anything the template does not ship; operator-specific facts go there or into `sync.json` vars, never into a synced file.
26
34
 
27
35
  ## Tickets and specs
28
36
 
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: add-feature
3
+ targets: ['*']
4
+ description: >
5
+ Deliver a feature end-to-end in this consumer repo. Aggregates context (Jira + Confluence + Slack +
6
+ Google Drive + Notion + local docs + past sessions + codebase), produces an approved plan, then drives
7
+ delivery by calling sibling skills - create-plugin (build), review (review), create-pr (MR) -
8
+ and create-task for ticket hygiene. Transitions Jira (no comments) and drafts a one-line Slack
9
+ notice. Use on "add feature", "plan {{trackerKey}}-XXX", "deliver {{trackerKey}}-XXX", or /add-feature [{{trackerKey}}-XXX].
10
+ Read-only until the plan is approved; never pushes, transitions Jira, or sends Slack without OK.
11
+ ---
12
+
13
+ # add-feature ({{name}})
14
+
15
+ Feature-delivery orchestrator for this consumer repo: one Jira key in; a delivered MR + updated ticket + drafted Slack notice out. You orchestrate and call sibling skills - you do not re-implement their work. The platform-core twin is the `/add-feature` skill in the platform OSS repo (`{{ossDir}}/.rulesync/skills/add-feature/`).
16
+
17
+ ## Coordinates
18
+
19
+ - Jira: the **Atlassian** MCP, cloudId `{{jiraCloudId}}`, project `{{trackerKey}}` (ticket keys look like `{{trackerKey}}-XXX`). Request/render content as markdown (`contentFormat` + `responseContentFormat: "markdown"`).
20
+ - Code forge: `{{gitRemotePath}}`, default target `{{mrTarget}}` - CLI and commands in `docs/agents/forge.md`.
21
+ - Slack: `{{teamChannel}}`, draft only.
22
+ - Repo: `apps/api` (Hono entry + extensions) consumes `@openora/*` upstream; UI apps, when present, sit beside it under `apps/`.
23
+ - **Hard rule:** `{{ossDir}}` (the linked OSS checkout) is read-only (guard-core hook + permission deny). Extend from the outside; core changes hand off - see `handoff.md`.
24
+
25
+ ## The contract
26
+
27
+ - **Read-only until the Step 3 plan is approved.** No edits, commits, pushes, Jira writes, or Slack sends before sign-off.
28
+ - Reuse sibling skills, don't reinvent: **create-task** (ticket format), **create-plugin** (build an overlay), **review** (review), **create-pr** (MR). Delegate code to subagents.
29
+
30
+ ## Steps
31
+
32
+ ### 1. Resolve input + enhance the ask
33
+
34
+ `{{trackerKey}}-XXX` from `$ARGUMENTS`; if absent, ask. Echo it back. Run the `enhance-prompt` pre-step on the ask before gathering context, so Step 2 pulls only what's relevant and Step 3 plans against a clear brief.
35
+
36
+ ### 2. Gather context in parallel (read-only)
37
+
38
+ Run together; skip any source that returns nothing. Read `handoff.md` only on core-change signals.
39
+
40
+ - Jira + Confluence: per `docs/agents/issue-tracker.md` - `{{trackerKey}}-XXX` whole: description, AC, every comment, every image viewed, parent epic, linked issues, and every linked Confluence page with its images and comments (`atlassian-read` for Jira + Confluence; the Atlassian MCP is for search and writes only - it returns no image bytes).
41
+ - Slack MCP: search public/private, read threads + canvases.
42
+ - Google Drive MCP: PRDs, specs.
43
+ - Notion: `ntn` CLI per `notion-memory` skill - prior decisions, lessons.
44
+ - Local docs: `{{ossDir}}/docs` (ADRs, `architecture.md`, `catalog.json`), repo READMEs, both `CLAUDE.md`.
45
+ - Past sessions: grep `~/.claude/projects/**` and `~/.claude/plans` for the ticket key.
46
+ - Codebase: `oss` MCP (read-only) + Explore - map touchpoints in `apps/*`.
47
+
48
+ ### 3. Plan + classify (the gate)
49
+
50
+ Synthesize into a plan and present it. Do NOT edit yet.
51
+
52
+ - **Goal** (1-2 lines) + **Acceptance criteria** (observable, testable). AC missing or vague in Jira? Draft them yourself and mark as proposed.
53
+ - **Decisions found** - each with source (who/where/date), so they aren't relitigated.
54
+ - **Edge cases + unknowns (grill)** - per AC, enumerate: empty/error states, authz negatives, concurrency/idempotency, jurisdiction/currency variants, migration impact. Each unknown becomes either an explicit assumption in the plan or a question below - none stay silent.
55
+ - **Open questions** - ask before proceeding if any blocks design; batch them in one round, don't trickle.
56
+ - **Implementation breakdown** - tasks mapped to files/packages + the owning subagent. Classify each:
57
+ - **downstream** -> overlay plugin / adapter swap / UI provider / config (build via `create-plugin`).
58
+ - **OSS-core** -> only fixable in `@openora/*`. Flag it; triggers `handoff.md`.
59
+ - **Risks / dependencies** - external services, OSS handoff, data/migrations.
60
+
61
+ Require explicit approval. Treat as plan mode even if the harness isn't.
62
+
63
+ ### 4. Build (delegate)
64
+
65
+ After approval, for **downstream** work: run the **create-plugin** skill for each overlay/adapter/page slice - it scaffolds, wires `extensions.config.ts`, and enforces boundaries + audit + db rules. The owning subagent (`builder`) also writes unit + integration tests as part of the deliverable. `deployer` only if infra changes; `debugger` on demand for build/runtime failures.
66
+
67
+ For **OSS-core** items: read `handoff.md`, write the work-order, STOP that slice, continue the rest. When implementation starts, transition Jira to In Progress (Step 7 - confirm first).
68
+
69
+ ### 5. Tests, then review
70
+
71
+ Cheap gates first, prove it works, only then spend review on working code:
72
+
73
+ 1. `/check` (typecheck + lint + unit). Don't proceed on red.
74
+ 2. Derive an e2e checklist from the AC (happy path, edge cases, authz negatives, error states); `qa` writes/runs Playwright specs in `apps/e2e`, drives `chrome-devtools` on failure. E2e failures go back to `builder` BEFORE any review - don't review code that doesn't work.
75
+ 3. **review** on the change set, passing what the e2e run proved so reviewers dig where tests can't reach; loop `[BLOCK]`/`[WARN]` fixes back through `builder`.
76
+ 4. After fixes: re-run `/check` always; re-run the affected e2e specs if any fix changed behavior (not needed for pure convention/style fixes).
77
+
78
+ ### 6. Open the MR
79
+
80
+ Run **create-pr**: it commits (`feat({{trackerKey}}-XXX): ...`), reports the SHA, asks for "yes push", pushes, and opens the pull request against `{{mrTarget}}` per `docs/agents/forge.md`, with the CODEOWNERS for the changed paths as reviewers. Never bypass its push-consent gate.
81
+
82
+ ### 7. Jira status transition (NOT comments)
83
+
84
+ Use the **Atlassian** MCP - fetch the transitions -> show current status + options -> **confirm** -> apply the matching one (In Progress when build starts, In Review when the MR opens). **No MR-link or status comments.**
85
+
86
+ ### 8. Draft Slack notice (one line)
87
+
88
+ Use the **Slack** MCP to draft a message to `{{teamChannel}}`: a single line - emoji + PR/task name as a link (e.g. `👉 <feature> - MR !NN`). **Draft only**, never direct-send.
89
+
90
+ ## Rules
91
+
92
+ - Read-only until the Step 3 plan is approved.
93
+ - Never push without an explicit per-action "yes push" (inherited from `create-pr`).
94
+ - Never transition Jira without confirming; show status + options first. No Jira comments.
95
+ - Slack is a one-line draft, never direct-send.
96
+ - Never edit `{{ossDir}}`; hand off via `handoff.md`. Prefer overlay/plugin/adapter/config.
97
+ - One MR = one concern. Split unrelated work.
@@ -1,18 +1,12 @@
1
1
  # OSS core handoff
2
2
 
3
- When an `add-feature` work item can only be fixed in `@openora/*` core, this consumer repo cannot
4
- edit it (the `guard-core.mjs` hook and `permissions.deny` block the linked OSS checkout). Hand off to
5
- the platform-core twin skill instead of fighting the guard.
3
+ When an `add-feature` work item can only be fixed in `@openora/*` core, this consumer repo cannot edit it (the `guard-core.mjs` hook and `permissions.deny` block `{{ossDir}}/**`). Hand off to the platform-core twin skill instead of fighting the guard.
6
4
 
7
5
  ## When to hand off
8
6
 
9
7
  - The fix requires changing a contract, schema, service, router, or platform seam in `@openora/*`.
10
- - An overlay plugin / adapter swap / UI provider override / config change genuinely cannot
11
- express the behavior.
12
- - Default bias: try to extend from the outside first. Only hand off when you've confirmed the
13
- outside-in path doesn't exist.
14
-
15
- If you're unsure, say so in the plan (Step 3) and let the user decide before writing the order.
8
+ - An overlay plugin / adapter swap / UI provider override / config change genuinely cannot express the behavior.
9
+ - Default bias: extend from the outside first; hand off only when you've confirmed the outside-in path doesn't exist. If unsure, say so in the plan (Step 3) and let the user decide before writing the order.
16
10
 
17
11
  ## Work-order template
18
12
 
@@ -27,7 +21,7 @@ Write to `~/.claude/plans/<ticket-key>-oss.md` so both repos and future sessions
27
21
 
28
22
  ## Unblocks
29
23
 
30
- consumer feature <ticket-key> - <which downstream slice depends on this>
24
+ {{name}} feature <ticket-key> - <which downstream slice depends on this>
31
25
 
32
26
  ## Core surface to change
33
27
 
@@ -43,16 +37,12 @@ consumer feature <ticket-key> - <which downstream slice depends on this>
43
37
 
44
38
  ## Dependency
45
39
 
46
- consumer MR <url-or-TBD> stays in draft until this merges and a new @openora/core/\* version is consumed.
40
+ {{name}} MR <url-or-TBD> stays in draft until this merges and a new @openora/core/\* version is consumed.
47
41
  ```
48
42
 
49
43
  ## Protocol
50
44
 
51
- 1. The consumer repo writes the work-order and **stops** that slice (keep delivering independent
52
- downstream slices meanwhile).
53
- 2. The user runs `/add-feature` inside the platform OSS repo (separate cwd/session). It reads
54
- the work-order, implements, and opens its own MR there.
55
- 3. The consumer MR stays draft/blocked until the OSS MR merges and the consumer bumps the consumed
56
- `@openora/*` version. Link the two by the work-order path and the ticket key.
57
- 4. Note the handoff in the Jira ticket and the consumer MR description so the dependency is
58
- visible.
45
+ 1. This consumer repo writes the work-order and **stops** that slice (keep delivering independent downstream slices meanwhile).
46
+ 2. The user runs `/add-feature` inside the platform OSS repo (`{{ossDir}}`, separate cwd/session). It reads the work-order, implements, and opens its own MR there.
47
+ 3. The consumer MR stays draft/blocked until the OSS MR merges and the consumer bumps the consumed `@openora/*` version. Link the two by the work-order path and the ticket key.
48
+ 4. Note the handoff in the Jira ticket and the consumer MR description so the dependency is visible.
@@ -1,19 +1,17 @@
1
1
  ---
2
2
  name: create-plugin
3
-
3
+ targets: ['*']
4
4
  description: >
5
- Guided creation of an extension - the right way to add behavior or swap a vendor without
5
+ Guided creation of a {{name}} extension - the right way to add behavior or swap a vendor without
6
6
  touching `@openora/*` core. Interviews for intent, classifies the seam (plugin / adapter /
7
7
  page / route), runs the matching scaffold command, wires extensions.config.ts, and enforces
8
8
  boundaries + audit + db rules. Use on "create plugin", "add an extension", "swap KYC/PSP",
9
9
  "mount a page", "/create-plugin <name>".
10
10
  ---
11
11
 
12
- # create-plugin
12
+ # create-plugin ({{name}})
13
13
 
14
- The platform is extended from the **outside only** (overlay plugin / adapter rebind / UI page /
15
- config) - never by editing `@openora/*`. This skill picks the correct seam and scaffolds it.
16
- Domain questions go to `expert` first; review the result with `review`.
14
+ The platform is extended from the **outside only** (overlay plugin / adapter rebind / UI page / config) - never by editing `@openora/*`. This skill picks the correct seam and scaffolds it. Domain questions go to `expert` first; review the result with `review`.
17
15
 
18
16
  ## 1. Classify the seam (ask if unclear)
19
17
 
@@ -24,23 +22,17 @@ Domain questions go to `expert` first; review the result with `review`.
24
22
  | Mount a react-sdk page on a route | page | `pnpm gen page <route>` |
25
23
  | Add one route to an existing add-on/overlay | route | `/scaffold-route <add-on> <METHOD> <path>` |
26
24
 
27
- If the behavior genuinely cannot be expressed from the outside, STOP - it's an OSS-core change.
28
- Hand off via the `add-feature` skill's `handoff.md`. Do not patch the linked OSS checkout.
25
+ If the behavior genuinely cannot be expressed from the outside, STOP - it's an OSS-core change. Hand off via the `add-feature` skill's `handoff.md`. Never patch `{{ossDir}}`.
29
26
 
30
27
  ## 2. Ground first
31
28
 
32
- - Read `.claude/rules/overview.md` (what you may and may not touch) and
33
- `docs/standards/database.md` (if the extension owns tables).
34
- - Inspect what already exists with the `oss` MCP: `catalog-overview`, `list-adapters` (token +
35
- default binding to swap), `list-routes` (collision check), `list-events`.
36
- - For a domain rule you can't safely assume (a limit, a KYC threshold, a jurisdiction behavior),
37
- spawn `expert` before scaffolding.
29
+ - Read `.claude/rules/overview.md` (what you may and may not touch), `.claude/rules/oss-boundaries.md`, and `.claude/rules/db-conventions.md` + `docs/standards/database.md` (if the extension owns tables).
30
+ - Inspect what already exists with the `oss` MCP: `catalog-overview`, `list-adapters` (token + default binding to swap), `list-routes` (collision check), `list-slots`, `list-events`.
31
+ - For a domain rule you can't safely assume (a limit, a KYC threshold, a jurisdiction behavior), spawn `expert` before scaffolding.
38
32
 
39
33
  ## 3. Scaffold + wire
40
34
 
41
- Run the command from step 1, then fill the `// AGENT: implement here` regions. Register the plugin
42
- in `apps/api/src/extensions.config.ts`. **Order matters for adapters** - last registration of a DI
43
- token wins, so list a swap AFTER the module that owns the default binding.
35
+ Run the command from step 1, then fill the `// AGENT: implement here` regions. Register the plugin in `apps/api/src/extensions.config.ts`. **Order matters for adapters** - last registration of a DI token wins, so list a swap AFTER the module that owns the default binding.
44
36
 
45
37
  ```ts
46
38
  // good - swap binds after the owning module, so it replaces the default
@@ -52,12 +44,9 @@ plugins: [myCustomPspAdapter, walletModule];
52
44
 
53
45
  ## 4. Non-negotiables
54
46
 
55
- - **Boundaries**: import only package entrypoints (`@openora/core`, not `.../src` or `.../dist`).
56
- No imports between extensions; cross-extension data goes through the oRPC client or a schema subpath.
57
- - **Tables**: live in the overlay's own `src/schema/index.ts`; follow `docs/standards/database.md`
58
- (snake_case, `timestamp({ withTimezone: true })`). Run `pnpm db:migrate` after.
59
- - **Audit every mutation**: each state-changing action emits a domain event the `audit` add-on
60
- subscribes to, or resolves `AUDIT_WRITER` and calls `record(...)`. A mutation with no audit is not done.
47
+ - **Boundaries**: import only package entrypoints (`@openora/core`, not `.../src` or `.../dist`). No imports between extensions; cross-extension data goes through the oRPC client or a schema subpath.
48
+ - **Tables**: live in the overlay's own `src/schema/index.ts`; follow `docs/standards/database.md` (snake_case, `timestamp({ withTimezone: true })`). Run `pnpm db:migrate` after.
49
+ - **Audit every mutation**: each state-changing action emits a domain event the `audit` add-on subscribes to, or resolves `AUDIT_WRITER` and calls `record(...)`. A mutation with no audit is not done.
61
50
  - **Validate at the edge**: Zod schemas for every route input/output; no inline `fetch`/SQL in handlers.
62
51
 
63
52
  ## 5. Verify