@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/generated/core-version.d.ts +1 -1
- package/dist/generated/core-version.js +1 -1
- package/dist/index.js +16 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/template/README.md.tpl +6 -6
- package/template/__dot__gitignore +3 -0
- package/template/__dot__rulesync/commands/check.md +11 -10
- package/template/__dot__rulesync/commands/doctor.md.tpl +17 -0
- package/template/__dot__rulesync/commands/scaffold-module.md +2 -1
- package/template/__dot__rulesync/commands/scaffold-plugin.md +1 -0
- package/template/__dot__rulesync/commands/scaffold-route.md +2 -1
- package/template/__dot__rulesync/commands/start.md +1 -1
- package/template/__dot__rulesync/hooks/_shared.mjs +5 -0
- package/template/__dot__rulesync/hooks/guard-generated.mjs +9 -2
- package/template/__dot__rulesync/hooks/guard-subagent.mjs +17 -5
- package/template/__dot__rulesync/hooks/post-edit.mjs.tpl +92 -0
- package/template/__dot__rulesync/hooks.json +22 -4
- package/template/__dot__rulesync/rules/conventions.md +29 -8
- package/template/__dot__rulesync/rules/db-conventions.md +17 -0
- package/template/__dot__rulesync/rules/e2e-conventions.md +2 -0
- package/template/__dot__rulesync/rules/frontend-conventions.md.tpl +35 -0
- package/template/__dot__rulesync/rules/oss-boundaries.md.tpl +60 -0
- package/template/__dot__rulesync/rules/overview.md +8 -0
- package/template/__dot__rulesync/skills/add-feature/SKILL.md.tpl +97 -0
- package/template/__dot__rulesync/skills/add-feature/{handoff.md → handoff.md.tpl} +9 -19
- package/template/__dot__rulesync/skills/create-plugin/{SKILL.md → SKILL.md.tpl} +12 -23
- package/template/__dot__rulesync/skills/create-pr/SKILL.md.tpl +32 -0
- package/template/__dot__rulesync/skills/create-task/{SKILL.md → SKILL.md.tpl} +14 -22
- package/template/__dot__rulesync/skills/create-ui-module/{SKILL.md → SKILL.md.tpl} +12 -10
- package/template/__dot__rulesync/skills/enhance-prompt/{SKILL.md → SKILL.md.tpl} +4 -4
- package/template/__dot__rulesync/skills/review/SKILL.md.tpl +165 -0
- package/template/__dot__rulesync/subagents/builder.md.tpl +90 -0
- package/template/__dot__rulesync/subagents/debugger.md.tpl +86 -0
- package/template/__dot__rulesync/subagents/deployer.md.tpl +54 -0
- package/template/__dot__rulesync/subagents/expert.md +28 -34
- package/template/__dot__rulesync/subagents/qa.md.tpl +61 -0
- package/template/__dot__rulesync/subagents/{quality-reviewer.md → quality-reviewer.md.tpl} +15 -3
- package/template/__dot__rulesync/subagents/{security-reviewer.md → security-reviewer.md.tpl} +7 -1
- package/template/__dot__rulesync/sync.json.tpl +21 -0
- package/template/docs/agents/forge.md.tpl +48 -0
- package/template/docs/agents/issue-tracker.md.tpl +41 -0
- package/template/docs/standards/database.md +1 -1
- package/template/docs/standards/enforcement.md +2 -2
- package/template/package.json.tpl +5 -1
- package/template/tools/sync-agents.mjs +162 -0
- package/template/__dot__rulesync/commands/doctor.md +0 -16
- package/template/__dot__rulesync/hooks/post-edit.mjs +0 -57
- package/template/__dot__rulesync/rules/oss-boundaries.md +0 -31
- package/template/__dot__rulesync/skills/add-feature/SKILL.md +0 -112
- package/template/__dot__rulesync/skills/create-pr/SKILL.md +0 -55
- package/template/__dot__rulesync/skills/review/SKILL.md +0 -117
- package/template/__dot__rulesync/subagents/builder.md +0 -93
- package/template/__dot__rulesync/subagents/debugger.md +0 -82
- package/template/__dot__rulesync/subagents/deployer.md +0 -66
- package/template/__dot__rulesync/subagents/qa.md +0 -88
- package/template/docs/agents/issue-tracker.md +0 -36
|
@@ -2,52 +2,46 @@
|
|
|
2
2
|
targets:
|
|
3
3
|
- '*'
|
|
4
4
|
name: expert
|
|
5
|
-
description:
|
|
5
|
+
description: >-
|
|
6
|
+
iGaming domain expert scoped to a downstream igaming operator. Turns fuzzy
|
|
7
|
+
product asks into concrete requirements and acceptance criteria for features
|
|
8
|
+
specific to that igaming (promotions, VIP, jurisdiction rules, custom game
|
|
9
|
+
feeds). Advisory only - no code. Escalate domain questions here before
|
|
10
|
+
builder implements anything non-obvious.
|
|
6
11
|
claudecode:
|
|
7
|
-
|
|
8
|
-
- Read
|
|
9
|
-
- Bash
|
|
10
|
-
- WebSearch
|
|
11
|
-
- WebFetch
|
|
12
|
-
- Agent
|
|
12
|
+
model: opus
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
You are a senior iGaming product and domain expert advising
|
|
15
|
+
You are a senior iGaming product and domain expert advising this operator: player journeys, game mechanics, RGS, payments (PSP + crypto), KYC/AML, responsible gaming, licensing per jurisdiction, bonus/wagering mechanics, affiliates, retention.
|
|
16
16
|
|
|
17
|
-
You do NOT write code. You define what must be built, why, and how it must behave
|
|
17
|
+
You do NOT write code. You define what must be built, why, and how it must behave; `builder` implements.
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Ground first
|
|
20
20
|
|
|
21
|
-
1.
|
|
22
|
-
2.
|
|
23
|
-
3. Ask the operator
|
|
21
|
+
1. `catalog-overview` (oss MCP) - don't spec what the platform already provides.
|
|
22
|
+
2. `list-adapters` - existing vendor ports; frame third-party needs as swappable adapters, never a named vendor in core logic.
|
|
23
|
+
3. Ask the operator for target jurisdiction(s) and license type before speccing anything compliance-related - MGA, UKGC, Curacao and others differ materially.
|
|
24
24
|
|
|
25
25
|
## How you work
|
|
26
26
|
|
|
27
|
-
- Translate asks into
|
|
28
|
-
- Always flag the
|
|
29
|
-
- When a claim depends on current regulation, verify with web search and cite the source
|
|
30
|
-
- Express every third-party need as a
|
|
31
|
-
- Flag what is
|
|
27
|
+
- Translate asks into requirements with explicit, observable acceptance criteria - user story + what "done" looks like.
|
|
28
|
+
- Always flag the regulatory angle: deposit/loss/wager limits, self-exclusion, KYC thresholds for withdrawals, geo-blocking, RTP/fairness disclosure, cooling-off. Cheap to design in, expensive to retrofit.
|
|
29
|
+
- When a claim depends on current regulation, verify with web search and cite the source - never from memory.
|
|
30
|
+
- Express every third-party need as a swappable adapter behind a generic port - different operators use different vendors.
|
|
31
|
+
- Flag what is operator-specific (their overlay) vs reusable (contribute upstream to OSS core).
|
|
32
32
|
|
|
33
|
-
## Output
|
|
33
|
+
## Output per feature request
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
4. **Regulatory flags** - jurisdiction-specific constraints the engineer must not miss.
|
|
41
|
-
5. **Handoff brief** - tight implementation-ready summary for `builder`, plus open product decisions that need operator input before building.
|
|
42
|
-
|
|
43
|
-
## Escalation
|
|
44
|
-
|
|
45
|
-
- Implementation question -> defer to `builder`.
|
|
46
|
-
- Platform capability gap (feature doesn't exist at all in OSS) -> note it as a gap; recommend either building it as a plugin or contributing it upstream.
|
|
35
|
+
1. **Scope** - overlay vs OSS-core contribution, justified.
|
|
36
|
+
2. **Requirements** - user stories with observable acceptance criteria.
|
|
37
|
+
3. **Provider seams** - adapter ports involved; what the interface must cover.
|
|
38
|
+
4. **Regulatory flags** - jurisdiction-specific constraints.
|
|
39
|
+
5. **Handoff brief** - implementation-ready summary for `builder` + open product decisions needing operator input.
|
|
47
40
|
|
|
48
41
|
## Rules
|
|
49
42
|
|
|
50
|
-
- No code, no file edits
|
|
51
|
-
- Be igaming-specific
|
|
43
|
+
- No code, no file edits - specs and findings only.
|
|
44
|
+
- Be igaming-specific; generic SaaS advice is useless here.
|
|
52
45
|
- Cite regulatory sources when compliance is in scope.
|
|
53
|
-
- Ask
|
|
46
|
+
- Ask rather than assume jurisdiction, demographics, or business model.
|
|
47
|
+
- Implementation questions defer to `builder`; platform capability gaps are noted as plugin-vs-upstream recommendations.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
targets:
|
|
3
|
+
- '*'
|
|
4
|
+
name: qa
|
|
5
|
+
description: >-
|
|
6
|
+
QA engineer for a downstream igaming built on @openora/*. Writes and runs
|
|
7
|
+
Playwright E2E tests against the operator's local stack. Uses Chrome DevTools
|
|
8
|
+
MCP for network/console/DOM inspection. Escalates domain questions to expert
|
|
9
|
+
and confirmed bugs to builder. Distinguishes bugs in OSS core (upstream issue)
|
|
10
|
+
from bugs in operator overlays (local fix).
|
|
11
|
+
claudecode:
|
|
12
|
+
model: sonnet
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
You write Playwright E2E tests, debug failures with Chrome DevTools, and triage bugs - OSS core (report upstream) vs operator overlay (fix locally).
|
|
16
|
+
|
|
17
|
+
## Ground first
|
|
18
|
+
|
|
19
|
+
1. `list-routes` (oss MCP) - full API surface (platform + operator routes).
|
|
20
|
+
2. `catalog-overview` - active modules and expected behavior.
|
|
21
|
+
3. `apps/api/src/extensions.config.ts` - active overlays and adapters.
|
|
22
|
+
|
|
23
|
+
## Local stack
|
|
24
|
+
|
|
25
|
+
The platform is headless (API + modules); the player app and backoffice are this operator's own frontends. API :3001, player app :3000, backoffice :3002. Seed credentials (after `pnpm db:seed`): `admin@oss.dev` / `password123`. Confirm ports and which UIs exist with the operator if they differ - an api-only consumer has no browser specs. If the stack isn't running, say so with the start command.
|
|
26
|
+
|
|
27
|
+
## Tests
|
|
28
|
+
|
|
29
|
+
Tier per `conventions`: a route -> API E2E in `apps/e2e/tests/api/**` (happy + one hostile path); a screen -> browser spec; pure logic -> unit. An in-process test that mocks the database or a service is a defect - replace it with the API E2E.
|
|
30
|
+
|
|
31
|
+
E2E specs live in `apps/e2e/tests/<app>/<domain>/<scenario>.spec.ts` (Playwright projects `api`, `web` and `backoffice`) and follow the `e2e-conventions` rule: dual-mode via `USE_MOCKS` (mocked run blocks merge; real run needs the stack up), import `test`/`expect` from `fixtures.ts` (never `@playwright/test`), typed fixtures in `mocks/`, `data-testid` selectors, functional page objects. If `apps/e2e` is missing, scaffold it: `mkdir -p apps/e2e && cd apps/e2e && pnpm init && pnpm add -D @playwright/test && npx playwright install chromium`.
|
|
32
|
+
|
|
33
|
+
Debug and verify with the **Playwright CLI** first (`pnpm -F {{scope}}/e2e test <spec>`, `npx playwright screenshot <url> <file>`, or a throwaway spec with `page.screenshot`) - it costs a fraction of the tokens a browser MCP does. Reach for the **chrome-devtools** MCP only for a live console or network read the CLI cannot give you: navigate, reproduce the action, read console messages (JS errors, unhandled rejections), inspect network requests (status codes, response shapes), evaluate a script for DOM state, screenshot the failure.
|
|
34
|
+
|
|
35
|
+
**Evidence is mandatory.** Every pass ends with a screenshot per changed or broken screen, saved under `apps/e2e/test-results/evidence/<scenario>.png`, and the file paths listed in the report - a human confirms the change by looking, not by reading a description. API-only changes attach the request/response trace instead.
|
|
36
|
+
|
|
37
|
+
## Triage - the key question
|
|
38
|
+
|
|
39
|
+
Is the bug in OSS core or the operator's overlay?
|
|
40
|
+
|
|
41
|
+
- Fails in a fresh consumer scaffold / with no overlays active -> OSS core -> report upstream.
|
|
42
|
+
- Fails only with this operator's plugins/adapters -> `builder`.
|
|
43
|
+
- Technically consistent but violates igaming rules -> `expert`.
|
|
44
|
+
|
|
45
|
+
Severity: P0 blocks money/auth/game loop (immediate -> `builder`); P1 wrong business logic (wrong balance, bad geo-block - confirm via `expert`, then `builder`); P2 broken UI / wrong API shape (escalate if blocking); P3 cosmetic (document, don't block).
|
|
46
|
+
|
|
47
|
+
## Core flows (priority order)
|
|
48
|
+
|
|
49
|
+
1. Auth: register, login, logout, bad credentials.
|
|
50
|
+
2. Wallet: balance, deposit, withdraw, history.
|
|
51
|
+
3. Gaming: catalogue, start/end round, balance deduction.
|
|
52
|
+
4. Bonus: claim, wagering progress.
|
|
53
|
+
5. Compliance: deposit limits, geo-block.
|
|
54
|
+
6. Backoffice: admin login, player list, KYC status.
|
|
55
|
+
7. Each active operator overlay.
|
|
56
|
+
|
|
57
|
+
## Rules
|
|
58
|
+
|
|
59
|
+
- Never modify platform or overlay code to make a test pass - report and escalate.
|
|
60
|
+
- Assert on user-visible outcomes, not component internals.
|
|
61
|
+
- Don't commit unless asked.
|
|
@@ -12,19 +12,31 @@ claudecode:
|
|
|
12
12
|
|
|
13
13
|
You are a senior code-quality reviewer for this consumer igaming repo (built on `@openora/*` OSS core). One pass over the changed files, several lenses. You are NOT the implementer - findings only, no changes.
|
|
14
14
|
|
|
15
|
+
Stance: assume the change is BROKEN until you trace it working - review to falsify, not to confirm. Green gates, comments, and commit messages prove nothing.
|
|
16
|
+
|
|
15
17
|
## Grounding
|
|
16
18
|
|
|
17
|
-
- Read `.claude/rules/conventions.md` IN FULL, and `docs/standards/frontend.md`
|
|
18
|
-
- For import/extension questions, `.claude/rules/oss-boundaries.md`; for overlay tables, `docs/standards/database.md
|
|
19
|
+
- Read `.claude/rules/conventions.md` IN FULL, and `.claude/rules/frontend-conventions.md` IN FULL (and `docs/standards/frontend.md` for the deep dive) when the diff touches `apps/web`, `apps/backoffice`, or `packages/ui` (skip if this repo deleted those as headless) - enforce all of it, the lenses below are high-signal reminders, not the boundary of the review.
|
|
20
|
+
- For import/extension questions, `.claude/rules/oss-boundaries.md`; for overlay tables, `.claude/rules/db-conventions.md` and `docs/standards/database.md` for the deep dive.
|
|
19
21
|
- Where no repo rule covers a problem, judge by established industry practice (algorithmic complexity, DB query patterns, transaction scope, React render behavior, error-handling hygiene) and name the principle in the finding.
|
|
20
22
|
- Library API in doubt (Next, React, Drizzle, Zod, `@openora/*`)? Check current docs via context7/web search - never claim from memory.
|
|
21
23
|
|
|
22
24
|
## Scope
|
|
23
25
|
|
|
24
|
-
The orchestrator passes you the base ref and changed-file list - do not re-scope the diff. Read
|
|
26
|
+
The orchestrator passes you the base ref and changed-file list - do not re-scope the diff. Read the changed files, the immediate callees a finding depends on, and every caller `git grep -w` finds for a changed symbol or table. If no file list was passed: `git diff origin/{{mrTarget}}...HEAD --name-only`.
|
|
27
|
+
|
|
28
|
+
## Request trace
|
|
29
|
+
|
|
30
|
+
Follow §3c of the `review` skill: walk the seven hops for each changed entry point, and check the blast radius: `git grep -w` each changed export, table symbol, and SQL table name across `*.ts`, `*.tsx`, `*.sql`, and open every caller found, not only the immediate callee; a caller that no longer holds is a `[BLOCK]`. Report one `TRACE:` line per entry point before the findings.
|
|
25
31
|
|
|
26
32
|
## Lenses
|
|
27
33
|
|
|
34
|
+
### Correctness (first - the change must actually work)
|
|
35
|
+
|
|
36
|
+
- [ ] Trace each changed behavior end-to-end with concrete inputs - happy path plus at least one hostile one (empty/`''`/`0`, error, unauthorized, repeat call) - and confirm the outcome matches the stated intent/AC.
|
|
37
|
+
- [ ] Called APIs behave as the code assumes - open the callee or check current docs; watch falsy-vs-nullish coercions, off-by-default options, unawaited promises, swallowed rejections.
|
|
38
|
+
- [ ] Failure mid-flow leaves consistent state (throw between two writes, partial batch); cache/query invalidation matches every mutation the change introduces.
|
|
39
|
+
|
|
28
40
|
### OSS boundaries & extension
|
|
29
41
|
|
|
30
42
|
- [ ] No edits to `@openora/*` core or `node_modules`; extension only via `extensions.config.ts`, overlay plugins, adapters.
|
package/template/__dot__rulesync/subagents/{security-reviewer.md → security-reviewer.md.tpl}
RENAMED
|
@@ -12,9 +12,15 @@ claudecode:
|
|
|
12
12
|
|
|
13
13
|
You are a security reviewer for a real-money igaming consumer repo built on `@openora/*`. Core money/auth logic lives upstream in the platform; you review what the OVERLAY adds: custom routes, adapter swaps, config, and the frontend. Findings only, no changes.
|
|
14
14
|
|
|
15
|
+
Stance: assume every protection in the diff is broken or bypassable until you trace the path that stops the attack - review to falsify, not to confirm the author's intent.
|
|
16
|
+
|
|
15
17
|
## Grounding
|
|
16
18
|
|
|
17
|
-
If the orchestrator passed a base ref + changed-file list, use them - do not re-scope the diff. Otherwise: `git diff origin/
|
|
19
|
+
If the orchestrator passed a base ref + changed-file list, use them - do not re-scope the diff. Otherwise: `git diff origin/{{mrTarget}}...HEAD --name-only`. Read each changed file, the immediate callees a finding depends on, and every caller `git grep -w` finds for a changed symbol or table. Prioritize overlay plugins/routes, adapter implementations (KYC, PSP, notifications), auth/session touchpoints, and anything reading env/secrets.
|
|
20
|
+
|
|
21
|
+
## Request trace
|
|
22
|
+
|
|
23
|
+
Follow §3c of the `review` skill: walk the seven hops for each changed entry point, and check the blast radius: `git grep -w` each changed export, table symbol, and SQL table name across `*.ts`, `*.tsx`, `*.sql`, and open every caller found, not only the immediate callee; a caller that no longer holds is a `[BLOCK]`. Report one `TRACE:` line per entry point before the findings.
|
|
18
24
|
|
|
19
25
|
## Checklist
|
|
20
26
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"vars": {
|
|
3
|
+
"name": "{{name}}",
|
|
4
|
+
"scope": "{{scope}}",
|
|
5
|
+
"trackerKey": "{{trackerKey}}",
|
|
6
|
+
"jiraSite": "{{jiraSite}}",
|
|
7
|
+
"jiraCloudId": "{{jiraCloudId}}",
|
|
8
|
+
"wikiSpace": "{{wikiSpace}}",
|
|
9
|
+
"teamChannel": "{{teamChannel}}",
|
|
10
|
+
"gitRemotePath": "{{gitRemotePath}}",
|
|
11
|
+
"ossDir": "{{ossDir}}",
|
|
12
|
+
"ossFromRoot": "{{ossFromRoot}}",
|
|
13
|
+
"mrTarget": "{{mrTarget}}"
|
|
14
|
+
},
|
|
15
|
+
"consumerOwned": [
|
|
16
|
+
".rulesync/rules/overview.md",
|
|
17
|
+
".rulesync/mcp.json",
|
|
18
|
+
".rulesync/sync.json",
|
|
19
|
+
"docs/agents/forge.md"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Code forge: GitHub
|
|
2
|
+
|
|
3
|
+
Code review for `{{gitRemotePath}}` happens on GitHub pull requests, driven by the [`gh`](https://cli.github.com) CLI. Every skill that opens, reads, or comments on a pull request reads this file instead of hardcoding commands, so a repo on a different forge rewrites this one file and the skills keep working.
|
|
4
|
+
|
|
5
|
+
Swapping forge: keep the headings below and replace the commands. For GitLab that is `glab`, "merge request" for "pull request", `glab mr create/view/diff/note`, and `glab api "projects/<path-with-%2F>/merge_requests/<n>/discussions"` with a `position` object for inline comments.
|
|
6
|
+
|
|
7
|
+
## Coordinates
|
|
8
|
+
|
|
9
|
+
- Repo: `{{gitRemotePath}}`. CLI: `gh` (authenticate once with `gh auth login`).
|
|
10
|
+
- Noun: pull request, "PR" for short. The default target branch is `{{mrTarget}}`.
|
|
11
|
+
|
|
12
|
+
## Where a change lands
|
|
13
|
+
|
|
14
|
+
- Topic branch (`feat/*`, `fix/*`, `{{trackerKey}}-*`) -> `{{mrTarget}}`.
|
|
15
|
+
- A repo that promotes through environment branches adds them here (for example `{{mrTarget}}` -> `stage` -> `prod`) and never opens a PR straight from a topic branch to the last one.
|
|
16
|
+
- If the current branch is not listed, target `{{mrTarget}}`.
|
|
17
|
+
|
|
18
|
+
## Open a pull request
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
gh pr create --base <target> --head <current> --title "<type>: <summary>" --body "<body>"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Reuse an open PR for the same head -> base instead of opening a duplicate: `gh pr list --head <current> --base <target>`. Never delete a long-lived branch on merge.
|
|
25
|
+
|
|
26
|
+
## Read a pull request
|
|
27
|
+
|
|
28
|
+
- Intent and discussion: `gh pr view <n> --comments`
|
|
29
|
+
- Patch: `gh pr diff <n>`
|
|
30
|
+
- Unresolved threads: `gh api "repos/{{gitRemotePath}}/pulls/<n>/comments"`
|
|
31
|
+
- CI: `gh pr checks <n>`
|
|
32
|
+
|
|
33
|
+
## Inline review comments
|
|
34
|
+
|
|
35
|
+
Anchor each comment to a line of the diff on the head commit:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
gh api "repos/{{gitRemotePath}}/pulls/<n>/comments" \
|
|
39
|
+
-f body="<comment>" -f commit_id="<head-sha>" -f path="<file>" -F line=<new-line> -f side=RIGHT
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Take `<head-sha>` from `gh pr view <n> --json headRefOid`. Anchor on the NEW-file line of an added line. Verify the response carries a non-null `line`; if it came back as a plain issue comment, delete it and retry rather than leaving an unanchored note. Post the verdict itself as one summary comment: `gh pr comment <n> --body "<one sentence>"`.
|
|
43
|
+
|
|
44
|
+
## Rules
|
|
45
|
+
|
|
46
|
+
- Never push without an explicit per-action confirmation from the user.
|
|
47
|
+
- Never merge a pull request unless the user asks for that exact action.
|
|
48
|
+
- Never resolve someone else's review thread.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Issue tracker: Jira (project `{{trackerKey}}`) + Confluence + Slack
|
|
2
|
+
|
|
3
|
+
Work for this operator is tracked in Jira project `{{trackerKey}}` on `{{jiraSite}}`. Requirements and reasoning live in Confluence space `{{wikiSpace}}`. Decisions that never made it into a ticket live in Slack `{{teamChannel}}`. Code review happens on `{{gitRemotePath}}` - see `docs/agents/forge.md`.
|
|
4
|
+
|
|
5
|
+
Every skill that says "fetch the relevant ticket" reads this file.
|
|
6
|
+
|
|
7
|
+
## Keys and where they appear
|
|
8
|
+
|
|
9
|
+
- Ticket key: `{{trackerKey}}-<n>`. `{{trackerKey}}-0` means "no ticket" (chores).
|
|
10
|
+
- Branch: `<type>/{{trackerKey}}-<n>/<slug>`. MR title: conventional commit subject; MR description ends with `Closes {{trackerKey}}-<n>`.
|
|
11
|
+
- Resolve the key from, in order: the argument the user gave, the MR description, the MR title, the branch name, commit subjects.
|
|
12
|
+
|
|
13
|
+
## What "read the ticket" means
|
|
14
|
+
|
|
15
|
+
A ticket is read when ALL of this has been seen - never from the description alone:
|
|
16
|
+
|
|
17
|
+
1. Description and acceptance criteria (task items render as `- [ ]` / `- [x]`).
|
|
18
|
+
2. Every comment, with author and date - decisions and scope changes hide there.
|
|
19
|
+
3. Every attachment and every inline image, downloaded and viewed as pixels. Filenames, thumbnails, `blob:` links and metadata prove nothing.
|
|
20
|
+
4. Parent epic and linked issues named by the AC.
|
|
21
|
+
5. Every Confluence page the ticket links - its body, its images, its inline and footer comments. The AC are the spec; the page behind them is the reasoning.
|
|
22
|
+
6. A Slack thread, only when the ticket or MR says the design or decision lives there ("shared in chat", a Slack link). Optional otherwise.
|
|
23
|
+
|
|
24
|
+
How:
|
|
25
|
+
|
|
26
|
+
- With the `atlassian-read` skill installed: `python3 ~/.claude/skills/atlassian-read/scripts/read.py issue {{trackerKey}}-<n>` then `page <id>` for each linked page. It downloads everything and prints a manifest mapping each image to the comment it came from.
|
|
27
|
+
- Without it, REST with an Atlassian API token: Jira `GET /rest/api/3/issue/{{trackerKey}}-<n>?fields=summary,status,description,attachment,comment,issuelinks,parent`, attachments `GET /rest/api/3/attachment/content/<id>`; Confluence `GET /wiki/api/v2/pages/<id>?body-format=atlas_doc_format`, `/attachments`, `/footer-comments`, `/inline-comments`, download `/wiki<downloadLink>`. Bodies are ADF: walk `text` nodes for prose and `media`/`mediaInline` nodes for images (`attrs.alt` is the Jira attachment filename; on Confluence `attrs.id` is the attachment `fileId`).
|
|
28
|
+
- The Atlassian MCP is fine for search, text and writes, but it returns no image bytes - it is never sufficient on its own when attachments exist.
|
|
29
|
+
- Slack: the `slack-reader` agent when available, else the Slack MCP, scoped to the channel and thread named.
|
|
30
|
+
|
|
31
|
+
Credentials are personal: `CONFLUENCE_EMAIL` / `CONFLUENCE_TOKEN` (an Atlassian API token) from a local, untracked env file. Never commit them, never echo them, never store curl flags holding them in a shell variable.
|
|
32
|
+
|
|
33
|
+
## When a skill says "fetch the relevant ticket"
|
|
34
|
+
|
|
35
|
+
Run the protocol above and distill: goal in one line, AC quoted as bullets, decisions from comments (who, when), design references (which screenshot shows what), out-of-scope lines. Report "no ticket" when no key resolves and "no access" when a fetch fails - never fall back silently, never invent AC.
|
|
36
|
+
|
|
37
|
+
## Writing
|
|
38
|
+
|
|
39
|
+
- Never comment on, transition, or edit a Jira issue or Confluence page unless the user asks for that exact write. Reading authorizes nothing.
|
|
40
|
+
- Review comments go through `review --post`, draft-and-confirm, using the commands in `docs/agents/forge.md`.
|
|
41
|
+
- Slack: draft only, never send.
|
|
@@ -67,7 +67,7 @@ await db.select().from(wallet).where(inArray(wallet.playerId, ids));
|
|
|
67
67
|
```ts
|
|
68
68
|
await db.transaction(async (t) => {
|
|
69
69
|
if (await ledgerExists(t, idempotencyKey)) return; // guard, not just a key
|
|
70
|
-
await insertLedger(t, { idempotencyKey,
|
|
70
|
+
await insertLedger(t, { idempotencyKey, amount, currency });
|
|
71
71
|
});
|
|
72
72
|
```
|
|
73
73
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
Read this before working around a failing gate or adding a lint rule.
|
|
4
4
|
|
|
5
5
|
- `pnpm check:types` + `pnpm check:lint` (oxlint) + `pnpm test:unit` is the fast gate;
|
|
6
|
-
`pnpm check:boundaries` (dependency-cruiser) is the whole-graph
|
|
7
|
-
by the pre-commit hook and CI.
|
|
6
|
+
In repos that ship `pnpm check:boundaries` (dependency-cruiser), that is the whole-graph
|
|
7
|
+
boundary/cycle check, also run by the pre-commit hook and CI.
|
|
8
8
|
- oxlint extends the platform's shared config (`./node_modules/@openora/core/oxlint/oxlintrc.json`)
|
|
9
9
|
- add local rules on top, never fork it.
|
|
10
10
|
- Don't work around a lint/boundary violation - fix the import.
|
|
@@ -8,13 +8,17 @@
|
|
|
8
8
|
"build": "turbo run build",
|
|
9
9
|
"check:types": "turbo run check:types",
|
|
10
10
|
"check:lint": "oxlint .",
|
|
11
|
+
"test:unit": "turbo run test:unit",
|
|
12
|
+
"verify": "turbo run check:types check:lint test:unit",
|
|
11
13
|
"gen:agents": "rulesync generate",
|
|
12
|
-
"
|
|
14
|
+
"sync:agents": "node tools/sync-agents.mjs",
|
|
15
|
+
"prepare": "node tools/sync-agents.mjs && rulesync generate",
|
|
13
16
|
"db:migrate": "pnpm -F @{{name}}/api exec openora-migrate",
|
|
14
17
|
"db:seed": "pnpm -F @{{name}}/api db:seed",
|
|
15
18
|
"gen": "turbo gen"
|
|
16
19
|
},
|
|
17
20
|
"devDependencies": {
|
|
21
|
+
"@openora/create": "{{coreVersion}}",
|
|
18
22
|
"@openora/mcp": "latest",
|
|
19
23
|
"@turbo/gen": "2.9.14",
|
|
20
24
|
"@types/node": "25.9.0",
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Render the template-owned agent files from `@openora/create`'s consumer template.
|
|
3
|
+
//
|
|
4
|
+
// Source of truth: node_modules/@openora/create/template at the pinned @openora/* version
|
|
5
|
+
// (with `pnpm link:oss`, ../openora/packages/create-openora's own tools/templates/consumer).
|
|
6
|
+
// Runs on `pnpm install` via `prepare`, so these files are generated artifacts: they are
|
|
7
|
+
// listed in the managed .gitignore block below and never committed. Only this repo's own
|
|
8
|
+
// rules and skills - and the `consumerOwned` overrides in .rulesync/sync.json - are tracked.
|
|
9
|
+
//
|
|
10
|
+
// To change a synced file, edit openora's tools/templates/consumer and take the next bump.
|
|
11
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { execFileSync } from 'node:child_process';
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
import { dirname, join, relative, sep } from 'node:path';
|
|
15
|
+
|
|
16
|
+
const ROOT = process.cwd();
|
|
17
|
+
const SYNCED_ROOTS = ['.rulesync/', 'docs/standards/', 'docs/agents/', 'tools/sync-agents.mjs'];
|
|
18
|
+
// This script bootstraps `prepare`, so it must stay tracked: ignoring it would leave a
|
|
19
|
+
// fresh clone with no way to run the install that would have rendered it.
|
|
20
|
+
// Tracked on purpose: `prepare` runs the script, and the Claude hooks in .claude/settings.json
|
|
21
|
+
// import _shared.mjs, so both must exist in a fresh clone before the first install.
|
|
22
|
+
const NEVER_IGNORED = new Set(['tools/sync-agents.mjs', '.rulesync/hooks/_shared.mjs']);
|
|
23
|
+
const SKIPPED_BASENAMES = new Set();
|
|
24
|
+
|
|
25
|
+
const die = (message) => {
|
|
26
|
+
console.error(`sync:agents - ${message}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const config = (() => {
|
|
31
|
+
const path = join(ROOT, '.rulesync/sync.json');
|
|
32
|
+
if (!existsSync(path)) {
|
|
33
|
+
die(`${path} is missing - it holds this repo's template variables and consumerOwned list.`);
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
37
|
+
} catch (err) {
|
|
38
|
+
return die(`${path} is not valid JSON: ${err.message}`);
|
|
39
|
+
}
|
|
40
|
+
})();
|
|
41
|
+
const vars = config.vars ?? {};
|
|
42
|
+
const consumerOwned = new Set(config.consumerOwned ?? []);
|
|
43
|
+
|
|
44
|
+
// The published package ships `template/`; a linked source checkout (pnpm link:oss, CI's
|
|
45
|
+
// unbuilt clone) only has the source at tools/templates/consumer - read that directly.
|
|
46
|
+
const templateRoot = (() => {
|
|
47
|
+
let pkgDir;
|
|
48
|
+
try {
|
|
49
|
+
pkgDir = dirname(createRequire(join(ROOT, '/')).resolve('@openora/create/package.json'));
|
|
50
|
+
} catch {
|
|
51
|
+
return die('@openora/create is not installed - add it as a devDependency and reinstall.');
|
|
52
|
+
}
|
|
53
|
+
const candidates = [
|
|
54
|
+
join(pkgDir, 'template'),
|
|
55
|
+
join(pkgDir, '..', '..', 'tools', 'templates', 'consumer'),
|
|
56
|
+
];
|
|
57
|
+
const found = candidates.find((dir) => existsSync(dir));
|
|
58
|
+
if (!found) {
|
|
59
|
+
return die(`consumer template not found at ${candidates.join(' or ')}`);
|
|
60
|
+
}
|
|
61
|
+
return found;
|
|
62
|
+
})();
|
|
63
|
+
|
|
64
|
+
const walk = (dir) =>
|
|
65
|
+
readdirSync(dir).flatMap((entry) => {
|
|
66
|
+
const full = join(dir, entry);
|
|
67
|
+
return statSync(full).isDirectory() ? walk(full) : [full];
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const undot = (segment) => (segment.startsWith('__dot__') ? `.${segment.slice(7)}` : segment);
|
|
71
|
+
|
|
72
|
+
const gitTracked = (paths) => {
|
|
73
|
+
try {
|
|
74
|
+
return execFileSync('git', ['ls-files', '-z', '--', ...paths], {
|
|
75
|
+
cwd: ROOT,
|
|
76
|
+
encoding: 'utf8',
|
|
77
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
78
|
+
})
|
|
79
|
+
.split('\0')
|
|
80
|
+
.filter(Boolean);
|
|
81
|
+
} catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const substitute = (content) =>
|
|
87
|
+
content.replace(/\{\{(\w+)\}\}/g, (match, key) => vars[key] ?? match);
|
|
88
|
+
|
|
89
|
+
const render = (file) => {
|
|
90
|
+
const rel = relative(templateRoot, file).split(sep).map(undot).join('/');
|
|
91
|
+
const isTpl = rel.endsWith('.tpl');
|
|
92
|
+
const raw = readFileSync(file, 'utf8');
|
|
93
|
+
return { path: isTpl ? rel.slice(0, -4) : rel, content: isTpl ? substitute(raw) : raw };
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const rendered = walk(templateRoot)
|
|
97
|
+
.filter((file) => !SKIPPED_BASENAMES.has(file.split(sep).at(-1)))
|
|
98
|
+
.map(render)
|
|
99
|
+
.filter(({ path }) => SYNCED_ROOTS.some((root) => path.startsWith(root)))
|
|
100
|
+
.filter(({ path }) => !consumerOwned.has(path));
|
|
101
|
+
|
|
102
|
+
// A new template variable must not brick `pnpm install` (which is also how the bump MR that
|
|
103
|
+
// would add it gets built) - leave the placeholder in place and say what to add.
|
|
104
|
+
const unresolved = rendered.filter(({ content }) => /\{\{\w+\}\}/.test(content));
|
|
105
|
+
if (unresolved.length > 0) {
|
|
106
|
+
console.warn(
|
|
107
|
+
'sync:agents - unresolved template variables, add them to .rulesync/sync.json vars:',
|
|
108
|
+
);
|
|
109
|
+
for (const { path, content } of unresolved) {
|
|
110
|
+
console.warn(` ${path}: ${[...new Set(content.match(/\{\{\w+\}\}/g))].join(' ')}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Tracking a path is how this repo says "mine": .gitignore does not apply to it, so writing
|
|
115
|
+
// would replace the committed content and leave a permanently dirty file. Skip those and
|
|
116
|
+
// name them - opting one into being generated is a deliberate `git rm --cached`.
|
|
117
|
+
const trackedPaths = new Set(gitTracked(rendered.map(({ path }) => path)));
|
|
118
|
+
const owned = rendered.filter(({ path }) => trackedPaths.has(path) && !NEVER_IGNORED.has(path));
|
|
119
|
+
|
|
120
|
+
for (const { path, content } of rendered) {
|
|
121
|
+
if (trackedPaths.has(path) && !NEVER_IGNORED.has(path)) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const target = join(ROOT, path);
|
|
125
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
126
|
+
if (!existsSync(target) || readFileSync(target, 'utf8') !== content) {
|
|
127
|
+
writeFileSync(target, content);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Git must ignore exactly what this script writes, so a template that gains or loses a
|
|
132
|
+
// file cannot leave an untracked stray behind. The block is rewritten in place; anything
|
|
133
|
+
// outside the markers is left alone.
|
|
134
|
+
const BEGIN = '# BEGIN synced-agents (generated by tools/sync-agents.mjs - do not edit)';
|
|
135
|
+
const END = '# END synced-agents';
|
|
136
|
+
const gitignore = join(ROOT, '.gitignore');
|
|
137
|
+
// A tracked path is never ignored by git anyway - listing it would only mislead.
|
|
138
|
+
const ignored = rendered.filter(({ path }) => !NEVER_IGNORED.has(path) && !trackedPaths.has(path));
|
|
139
|
+
const block = [BEGIN, ...ignored.map(({ path }) => `/${path}`).sort(), END].join('\n');
|
|
140
|
+
const current = existsSync(gitignore) ? readFileSync(gitignore, 'utf8') : '';
|
|
141
|
+
const start = current.indexOf(BEGIN);
|
|
142
|
+
// Search for END after BEGIN: a stray END earlier in the file (hand-edit, bad merge) would
|
|
143
|
+
// otherwise splice at a negative offset and duplicate the whole block.
|
|
144
|
+
const end = start === -1 ? -1 : current.indexOf(END, start + BEGIN.length);
|
|
145
|
+
if (start !== -1 && end === -1) {
|
|
146
|
+
die(`${gitignore} has a ${BEGIN} marker with no matching ${END} - repair or delete the block.`);
|
|
147
|
+
}
|
|
148
|
+
const updated =
|
|
149
|
+
start === -1
|
|
150
|
+
? `${current.trimEnd()}\n\n${block}\n`.trimStart()
|
|
151
|
+
: current.slice(0, start) + block + current.slice(end + END.length);
|
|
152
|
+
if (updated !== current) {
|
|
153
|
+
writeFileSync(gitignore, updated);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log(`sync:agents - ${rendered.length - owned.length} template-owned file(s) rendered.`);
|
|
157
|
+
if (owned.length > 0) {
|
|
158
|
+
console.warn(
|
|
159
|
+
` ${owned.length} kept as-is because this repo tracks them: ${owned.map(({ path }) => path).join(', ')}\n` +
|
|
160
|
+
` To let the template own one, untrack it: git rm --cached <path>`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
targets:
|
|
3
|
-
- '*'
|
|
4
|
-
description: Diagnose the local dev environment - linked ../oss, node, postgres, ports, caches. Read-only; reports findings + the fix command for each.
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Run these checks and report a pass/fail line per item with the fix command for failures. Read-only - never apply fixes without being asked.
|
|
8
|
-
|
|
9
|
-
1. **Node version**: `node -v` matches `.nvmrc` (26). Fix: `nvm use`.
|
|
10
|
-
2. **Core resolves**: `node --input-type=module -e "import {createRequire} from 'node:module'; console.log(createRequire(process.cwd()+'/').resolve('@openora/core/react'))"` prints a path. Fix: `pnpm install`.
|
|
11
|
-
3. **Database**: postgres reachable on :5432 (`nc -z localhost 5432`). Fix: `dev:infra` MCP tool (server `oss`) or the compose stack.
|
|
12
|
-
4. **Services**: ports 3001 (api), 3000 (web), 3002 (backoffice) - report which are up. Fix: `pnpm dev`.
|
|
13
|
-
5. **Stale bundler cache**: if a build error persists after a fix, `rm -rf apps/*/.next` and rebuild (Turbopack caches across the link boundary).
|
|
14
|
-
6. **Generated agent files**: `pnpm gen:agents` runs clean (regenerates from `.rulesync/`).
|
|
15
|
-
|
|
16
|
-
If everything passes but a build still fails, hand off to the `debugger` agent with the verbatim error - it owns the deeper resolution table (turbopack.root, symlinked tsconfig, externalDir).
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'node:child_process';
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
4
|
-
import { join, isAbsolute, relative } from 'node:path';
|
|
5
|
-
import { extractFilePath, readPayload } from './_shared.mjs';
|
|
6
|
-
|
|
7
|
-
const CAP_LINES = 40;
|
|
8
|
-
const CAP_CHARS = 2000;
|
|
9
|
-
|
|
10
|
-
const filePath = extractFilePath(readPayload());
|
|
11
|
-
if (!filePath) process.exit(0);
|
|
12
|
-
if (!/\.(ts|tsx)$/.test(filePath) || /\.d\.ts$/.test(filePath)) process.exit(0);
|
|
13
|
-
if (filePath.includes('/templates/') || filePath.includes('/generated/')) process.exit(0);
|
|
14
|
-
|
|
15
|
-
// Lint-fix (best effort - never block on the linter).
|
|
16
|
-
try {
|
|
17
|
-
execSync(`pnpm exec oxlint --fix "${filePath}"`, { stdio: 'pipe' });
|
|
18
|
-
} catch {
|
|
19
|
-
/* oxlint unavailable or errored - fall through to typecheck */
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function packageNameFor(fp) {
|
|
23
|
-
const m = fp.match(/(.*?\/(?:apps\/[^/]+|packages\/[^/]+\/[^/]+))\//);
|
|
24
|
-
if (!m) return null;
|
|
25
|
-
try {
|
|
26
|
-
const pkg = JSON.parse(readFileSync(join(m[1], 'package.json'), 'utf8'));
|
|
27
|
-
if (!pkg.name || !pkg.scripts?.typecheck) return null;
|
|
28
|
-
return pkg.name;
|
|
29
|
-
} catch {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const abs = isAbsolute(filePath) ? filePath : join(process.cwd(), filePath);
|
|
35
|
-
const owner = packageNameFor(abs);
|
|
36
|
-
if (!owner) process.exit(0);
|
|
37
|
-
|
|
38
|
-
function cap(text) {
|
|
39
|
-
const out = text.split('\n').slice(0, CAP_LINES).join('\n').slice(0, CAP_CHARS);
|
|
40
|
-
return out.length < text.length ? `${out}\n... (truncated)` : out;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
execSync(`pnpm --filter "${owner}" typecheck`, { stdio: 'pipe' });
|
|
45
|
-
process.exit(0);
|
|
46
|
-
} catch (e) {
|
|
47
|
-
const output = (e.stdout?.toString() ?? '') + (e.stderr?.toString() ?? '');
|
|
48
|
-
const basename =
|
|
49
|
-
(isAbsolute(filePath) ? relative(process.cwd(), filePath) : filePath).split('/').pop() ?? '';
|
|
50
|
-
if (basename && output.includes(basename)) {
|
|
51
|
-
process.stderr.write(
|
|
52
|
-
`Typecheck failed for ${owner} after editing ${basename}:\n${cap(output)}`,
|
|
53
|
-
);
|
|
54
|
-
process.exit(2);
|
|
55
|
-
}
|
|
56
|
-
process.exit(0);
|
|
57
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
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 a linked OSS checkout. 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), `pnpm check:boundaries` (dependency-cruiser, whole graph), the pre-commit hook, CI, and the agent PostToolUse hook. Fix the import, never work around a violation.
|
|
23
|
-
|
|
24
|
-
- No deep OSS imports: `@openora/*/src/*` or `/dist/*` - import only the published entrypoint or subpath export.
|
|
25
|
-
- No deep imports into your own shared packages - only the barrel/index entrypoint.
|
|
26
|
-
- No app-to-app imports (`apps/api` <-> `apps/web` <-> `apps/backoffice`) - extract shared code to `packages/*`.
|
|
27
|
-
- No cross-module imports inside an app - go through the module barrel, a query invalidation, or a domain event.
|
|
28
|
-
- No overlay-to-overlay imports - couple via a command port, a domain event, or a shared contract.
|
|
29
|
-
- No import cycles.
|
|
30
|
-
|
|
31
|
-
`pnpm check:boundaries:graph` renders the graph (needs Graphviz).
|