@openora/create 0.4.1-canary.35 → 0.4.1-canary.351

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 (98) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/generated/versions.d.ts +11 -0
  3. package/dist/generated/versions.d.ts.map +1 -0
  4. package/dist/generated/versions.js +11 -0
  5. package/dist/generated/versions.js.map +1 -0
  6. package/dist/index.js +48 -93
  7. package/dist/index.js.map +1 -1
  8. package/dist/render.d.ts +15 -0
  9. package/dist/render.d.ts.map +1 -0
  10. package/dist/render.js +125 -0
  11. package/dist/render.js.map +1 -0
  12. package/package.json +8 -6
  13. package/template/README.md.tpl +12 -7
  14. package/template/__dot__claude/settings.json.tpl +22 -12
  15. package/template/__dot__env.example +10 -0
  16. package/template/__dot__gitignore +5 -0
  17. package/template/__dot__rulesync/commands/check.md +11 -10
  18. package/template/__dot__rulesync/commands/doctor.md.tpl +17 -0
  19. package/template/__dot__rulesync/commands/scaffold-module.md +3 -2
  20. package/template/__dot__rulesync/commands/scaffold-plugin.md +19 -3
  21. package/template/__dot__rulesync/commands/scaffold-route.md +1 -0
  22. package/template/__dot__rulesync/commands/start.md +2 -2
  23. package/template/__dot__rulesync/hooks/_shared.mjs +26 -13
  24. package/template/__dot__rulesync/hooks/guard-core.mjs.tpl +76 -28
  25. package/template/__dot__rulesync/hooks/guard-generated.mjs +15 -13
  26. package/template/__dot__rulesync/hooks/guard-mr.mjs +81 -0
  27. package/template/__dot__rulesync/hooks/guard-subagent.mjs +22 -6
  28. package/template/__dot__rulesync/hooks/post-edit.mjs.tpl +116 -0
  29. package/template/__dot__rulesync/hooks.json +27 -4
  30. package/template/__dot__rulesync/rules/conventions.md +99 -119
  31. package/template/__dot__rulesync/rules/db-conventions.md +6 -72
  32. package/template/__dot__rulesync/rules/e2e-conventions.md +33 -7
  33. package/template/__dot__rulesync/rules/frontend-conventions.md.tpl +35 -0
  34. package/template/__dot__rulesync/rules/oss-boundaries.md.tpl +78 -0
  35. package/template/__dot__rulesync/rules/overview.md +18 -82
  36. package/template/__dot__rulesync/skills/create-plugin/{SKILL.md → SKILL.md.tpl} +12 -23
  37. package/template/__dot__rulesync/skills/create-pr/SKILL.md.tpl +38 -0
  38. package/template/__dot__rulesync/skills/create-task/{SKILL.md → SKILL.md.tpl} +14 -22
  39. package/template/__dot__rulesync/skills/create-ui-module/{SKILL.md → SKILL.md.tpl} +12 -10
  40. package/template/__dot__rulesync/skills/enhance-prompt/{SKILL.md → SKILL.md.tpl} +4 -4
  41. package/template/__dot__rulesync/skills/handoff/SKILL.md +95 -0
  42. package/template/__dot__rulesync/skills/review/SKILL.md.tpl +277 -0
  43. package/template/__dot__rulesync/skills/ship/SKILL.md.tpl +157 -0
  44. package/template/__dot__rulesync/skills/ship/handoff.md.tpl +48 -0
  45. package/template/__dot__rulesync/skills/ship/watch-pr.mjs +172 -0
  46. package/template/__dot__rulesync/skills/ui-verify/SKILL.md.tpl +120 -0
  47. package/template/__dot__rulesync/skills/ui-verify/layout-check.check.sh +58 -0
  48. package/template/__dot__rulesync/skills/ui-verify/layout-check.js +163 -0
  49. package/template/__dot__rulesync/subagents/builder.md.tpl +115 -0
  50. package/template/__dot__rulesync/subagents/compliance-reviewer.md +78 -0
  51. package/template/__dot__rulesync/subagents/debugger.md.tpl +86 -0
  52. package/template/__dot__rulesync/subagents/deployer.md.tpl +54 -0
  53. package/template/__dot__rulesync/subagents/expert.md +28 -34
  54. package/template/__dot__rulesync/subagents/planner.md.tpl +39 -0
  55. package/template/__dot__rulesync/subagents/quality-reviewer.md.tpl +112 -0
  56. package/template/__dot__rulesync/subagents/security-reviewer.md.tpl +85 -0
  57. package/template/__dot__rulesync/subagents/tester.md.tpl +102 -0
  58. package/template/__dot__rulesync/sync.json.tpl +26 -0
  59. package/template/apps/api/package.json.tpl +2 -2
  60. package/template/apps/api/src/extensions.config.ts.tpl +12 -0
  61. package/template/apps/api/src/main.ts.tpl +1 -4
  62. package/template/apps/api/src/seed.ts.tpl +8 -1
  63. package/template/docker-compose.yml +13 -0
  64. package/template/docs/agents/cross-repo.md.tpl +37 -0
  65. package/template/docs/agents/forge.md.tpl +54 -0
  66. package/template/docs/agents/issue-tracker.md.tpl +42 -0
  67. package/template/docs/standards/comments.md +16 -0
  68. package/template/docs/standards/database.md +77 -0
  69. package/template/docs/standards/enforcement.md +12 -0
  70. package/template/docs/standards/errors.md +30 -0
  71. package/template/docs/standards/frontend.md +60 -0
  72. package/template/docs/standards/functions.md +38 -0
  73. package/template/docs/standards/git-delivery.md +20 -0
  74. package/template/docs/standards/module-structure.md +37 -0
  75. package/template/docs/standards/testing.md +88 -0
  76. package/template/docs/standards/types.md +31 -0
  77. package/template/package.json.tpl +15 -9
  78. package/template/rulesync.jsonc +3 -3
  79. package/template/tools/oss-worktree.mjs.tpl +105 -0
  80. package/template/tools/review-precheck.mjs +531 -0
  81. package/template/tools/sync-agents.mjs +257 -0
  82. package/dist/generated/core-version.d.ts +0 -2
  83. package/dist/generated/core-version.d.ts.map +0 -1
  84. package/dist/generated/core-version.js +0 -2
  85. package/dist/generated/core-version.js.map +0 -1
  86. package/template/__dot__rulesync/commands/doctor.md +0 -16
  87. package/template/__dot__rulesync/hooks/post-edit.mjs +0 -57
  88. package/template/__dot__rulesync/rules/oss-boundaries.md +0 -31
  89. package/template/__dot__rulesync/skills/add-feature/SKILL.md +0 -113
  90. package/template/__dot__rulesync/skills/add-feature/handoff.md +0 -58
  91. package/template/__dot__rulesync/skills/create-pr/SKILL.md +0 -55
  92. package/template/__dot__rulesync/skills/review/SKILL.md +0 -110
  93. package/template/__dot__rulesync/subagents/builder.md +0 -93
  94. package/template/__dot__rulesync/subagents/debugger.md +0 -82
  95. package/template/__dot__rulesync/subagents/deployer.md +0 -66
  96. package/template/__dot__rulesync/subagents/qa.md +0 -88
  97. package/template/__dot__rulesync/subagents/quality-reviewer.md +0 -67
  98. package/template/__dot__rulesync/subagents/security-reviewer.md +0 -54
@@ -0,0 +1,20 @@
1
+ # Git and delivery
2
+
3
+ Read this before a commit, PR, or branch operation.
4
+
5
+ - Conventional commits, enforced by commitlint (husky locally, CI on the PR/MR title): `feat`, `fix`, `refactor`, `chore`,
6
+ `docs`, `test`, `ci`, `perf`. E.g. `feat(wallet): atomic debit command port`.
7
+ - Subject starts lowercase, acronyms included (`feat(pam): kyc status filter`). This applies to
8
+ the PR title too - squash merges derive the commit message from it.
9
+ - One PR = one concern. Stage files explicitly; never `git add -A` when foreign changes are in
10
+ the tree.
11
+ - Green before review: `pnpm verify` on the committed tree. It is the full gate and records the
12
+ verified commit; the `guard-mr` hook blocks opening the PR/MR without it.
13
+ - Branch off `dev`; never commit directly to a shared branch; never push without an explicit
14
+ per-action "yes push", except the ticket branch `/ship` opens its MR from.
15
+ - PR description carries intent: what / why / acceptance criteria / ticket link. No secrets,
16
+ internal hostnames, or PII - it is the public record.
17
+ - A PR that adds or changes a flow across components (request path, state machine, async job or
18
+ webhook, a new module, overlay or vendor binding) carries a Mermaid `flowchart` or
19
+ `sequenceDiagram` of it: nodes named as in the code, under ~15 nodes, refusal and failure
20
+ branches included. No flow, no diagram.
@@ -0,0 +1,37 @@
1
+ # Package and module structure
2
+
3
+ Read this before creating a package, an overlay add-on, or wiring a new module's internal layout.
4
+
5
+ - One package = one concern, named `@<scope>/<kebab>`, with an explicit `exports` map. The
6
+ entrypoint IS the public API; everything else is internal and off-limits to consumers
7
+ (`oss-boundaries`).
8
+ - Overlay/add-on packages mirror the platform module shape: `contract/`, `schema/`, `service/`,
9
+ `router/`, `adapters/`, `__tests__/`, plus `plugin.ts` at the root as the single wiring point.
10
+
11
+ | Layer | File | Holds | Must NOT hold |
12
+ | -------- | --------------------------- | --------------------------------------------------------------- | -------------------------------- |
13
+ | schema | `schema/index.ts` | Drizzle `pgTable`s; row types via `$inferSelect`/`$inferInsert` | logic |
14
+ | contract | `contract/index.ts` | oRPC route contract + req/res Zod schemas | logic, transport wiring |
15
+ | service | `service/<name>.service.ts` | ALL business logic; emits events after DB commit | HTTP/transport knowledge |
16
+ | router | `router/index.ts` | thin oRPC wiring: resolve caller, call service, map errors | business rules |
17
+ | plugin | `plugin.ts` | DI wiring only: `ctx.provide(...)`, route registration | logic |
18
+ | adapters | `adapters/<vendor>/` | concrete impls of adapter ports | being imported by another module |
19
+
20
+ - Each overlay owns its `drizzle.config.ts` and its own migration history - never share one
21
+ migration folder across packages.
22
+ - Pin exact dependency versions in every package; a package never depends on an app.
23
+ - Add a dependency deliberately - std lib or a few lines often beat a tree.
24
+
25
+ ```ts
26
+ // bad - a service reaches into the container and hides what it depends on
27
+ export class WalletService {
28
+ constructor(private readonly container: Container) {}
29
+ }
30
+ // good - deps are constructor params of their port type; plugin.ts does the resolving
31
+ export class WalletService {
32
+ constructor(
33
+ private readonly db: DrizzleService,
34
+ private readonly payments: PaymentAdapter,
35
+ ) {}
36
+ }
37
+ ```
@@ -0,0 +1,88 @@
1
+ # Testing
2
+
3
+ Read this before adding or restructuring a test.
4
+
5
+ ## When a test is written
6
+
7
+ A feature or fix PR carries **its own tests**, written in the same branch from the manual
8
+ verification pass that proves the change works. There is no separate test PR.
9
+
10
+ Before the PR opens:
11
+
12
+ 1. Drive the change against the running stack. Reach for the **Playwright CLI** first
13
+ (`playwright-cli`, or a throwaway spec that clicks through the flow) - it costs a fraction of
14
+ the tokens a browser MCP does. Use a browser MCP only for a live console or network read the CLI
15
+ cannot give you. No videos in this pass, except a fix's "before" clip, taken when you reproduce
16
+ the bug: the code can still change before review is clean. Every changed screen gets the
17
+ `ui-verify` skill's screenshot review: four shots (mobile and desktop, light and dark), each
18
+ read for layout defects and given a PASS or FAIL line. Screenshots are for the agent, videos
19
+ for the reviewer.
20
+ 2. Capture the evidence once, on the final commit, after the tests are green and review is clean:
21
+ one short video per acceptance criterion, ending on the outcome it names, and the "after" twin
22
+ of a fix's "before" clip. Run every browser spec that drives an AC in one command with video on
23
+ (`test.use({ video: 'on' })` or the repo's record switch); `playwright-cli` with `video-start` /
24
+ `video-stop` only for an AC no spec drives. An API-only change attaches the request/response
25
+ trace instead.
26
+ 3. Attach that evidence **to the PR description**, not only to your report - a reviewer confirms a
27
+ change by looking at it.
28
+ 4. Write the tests that pass exercised, in this branch, at the tier the table below names. Include
29
+ the hostile paths and the authz negatives. A fix carries the test that fails without it.
30
+ 5. End the description with a **"Tests"** list: one line per test added, naming its tier
31
+ (unit / API E2E / browser E2E) and path.
32
+
33
+ A changed behaviour with no test is a review `[WARN]`, never a `[BLOCK]`, except on the paths below.
34
+
35
+ ## Tests that catch the bug
36
+
37
+ A test that still passes when the code it covers is broken checks nothing. On a money, KYC,
38
+ responsible-gambling or authz path, break each changed check on purpose (flip a condition, move a
39
+ boundary by one, drop an owner check, change the sign of an amount) and run the tests: at least one
40
+ must fail. The tester does this in its pass and lists each break as a mutation row in its verdict
41
+ table; a break no test catches on those paths holds the PR until a test catches it. Where the repo
42
+ has a `test:mutation` script, it does the breaking for you; the repo-wide mutation score is shown,
43
+ not a gate.
44
+
45
+ ## Pick the tier
46
+
47
+ Pick the OUTERMOST tier that can reach the behaviour - a test earns its keep by running real code,
48
+ not by being cheap.
49
+
50
+ | What you changed | Tier |
51
+ | ------------------------------------------------------------- | --------------------------------------------- |
52
+ | A screen, a form, a player/admin journey | Browser E2E (`apps/e2e/tests/<app>/**`) |
53
+ | An API route, an overlay, a vendor adapter, anything with SQL | API E2E (`apps/e2e/tests/api/**`) |
54
+ | A pure function - parser, resolver, mapper, money calculation | Unit (`src/__tests__/<name>.test.ts`, Vitest) |
55
+
56
+ The API tier drives the real API over HTTP against real Postgres, with each external vendor
57
+ replaced by a stub HTTP server the API is pointed at by env. It is the default tier for
58
+ `apps/api/src/extensions/**`: an overlay is wiring, and wiring is exactly what a unit test skips.
59
+
60
+ ## What is real, what is doubled
61
+
62
+ - **Anything that touches the database is tested against real Postgres.** Never fake a query
63
+ builder: a stubbed chain proves a call order, not a result, so it misses the regressions that
64
+ matter - a unique-index conflict, a wrong `where`, a lost race, a rollback that never happened.
65
+ - **External vendors are stubbed at their HTTP boundary** (a `node:http` stand-in the API's
66
+ base-URL env var points at), never by mocking our own adapter - a mocked adapter skips exactly
67
+ the wiring the test exists to check. Every vendor adapter therefore accepts a base-URL override.
68
+ - **A spy assertion is never the point of a test.** `expect(client.x).toHaveBeenCalledWith(...)`
69
+ only restates the line above it. A spy may stand in for an outbound vendor call, never for the
70
+ thing under test.
71
+
72
+ ```ts
73
+ // bad - a mocked builder chain proves a call order, not a result
74
+ const db = { select: vi.fn().mockReturnValue({ from: vi.fn().mockResolvedValue([row]) }) };
75
+ expect(db.select).toHaveBeenCalled();
76
+ // good - assert the outcome
77
+ expect(await service.get(id)).toEqual(row);
78
+ ```
79
+
80
+ ## How to write them
81
+
82
+ - Test behavior, not implementation - tests must survive a safe refactor (assert outputs, not
83
+ private caches).
84
+ - Cover the change in the same PR as the change; always include the authz negatives.
85
+ - Drive a vendor's inbound side the way the vendor does: post the real webhook shape to the real
86
+ route with a signature the stub's key material produces. Never call the adapter directly.
87
+ - Deterministic and isolated: no shared mutable state, no real outbound network, seedable data.
88
+ Own the rows a test creates and clean them up - the API suite shares one database.
@@ -0,0 +1,31 @@
1
+ # Types
2
+
3
+ Read this before adding or changing a schema, type, or enum-like value set.
4
+
5
+ - One source of truth per shape - infer, never hand-write a type that already exists:
6
+ `z.infer<typeof UserSchema>`, `typeof users.$inferSelect`, `Omit<User, 'id'>`.
7
+ - Schema-first at every boundary (HTTP, config, env, messages, events): validate once at the edge,
8
+ trust the type after. oRPC + Zod does this for routes; do the same elsewhere.
9
+ - Never re-infer a schema you imported - the owning contract exports the type once; import it.
10
+ - No `any` outside tests - `unknown` + narrowing.
11
+ - No `as` casts to silence the compiler - fix the root cause (`as const` is fine).
12
+ - Never `!` non-null assertions - narrow explicitly, or restructure so the value is provably
13
+ present (carry it on the object instead of re-looking it up).
14
+ - Under `noUncheckedIndexedAccess`: `.at()`, destructure-with-default
15
+ (`const [first = ''] = parts`), or an explicit guard - never `arr[i]!`.
16
+ - `type` over `interface` (lint-enforced).
17
+ - Type entity ids through their owning type (`playerId: Player['id']`), never a bare `string`.
18
+ - Derive related schemas with `.pick()/.omit()/.partial()/.extend()/.merge()` - never re-type
19
+ fields.
20
+ - Enum-like value sets are a values + schema + type triple declared once on the contract surface:
21
+ `X_STATUSES = [...] as const` -> `XStatusSchema = z.enum(X_STATUSES)` -> inferred `XStatus`.
22
+ Never a TS `enum`, never a second hand-typed copy - import the one from `@openora/*`.
23
+ - Make illegal states unrepresentable - discriminated unions
24
+ (`{ status: 'ok'; data } | { status: 'error'; error }`) over optional-flag soup.
25
+
26
+ ```ts
27
+ // bad - hand-written duplicate of an inferrable type
28
+ type User = { id: string; email: string; roles: string[] };
29
+ // good - infer from the schema that already validates the shape
30
+ type User = z.infer<typeof UserSchema>;
31
+ ```
@@ -8,25 +8,31 @@
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 && node .rulesync/hooks/guard-mr.mjs --stamp",
11
13
  "gen:agents": "rulesync generate",
12
- "prepare": "rulesync generate",
14
+ "sync:agents": "node tools/sync-agents.mjs",
15
+ "oss:worktree": "node tools/oss-worktree.mjs",
16
+ "review:precheck": "node tools/review-precheck.mjs",
17
+ "prepare": "node tools/sync-agents.mjs && rulesync generate",
13
18
  "db:migrate": "pnpm -F @{{name}}/api exec openora-migrate",
14
19
  "db:seed": "pnpm -F @{{name}}/api db:seed",
15
20
  "gen": "turbo gen"
16
21
  },
17
22
  "devDependencies": {
23
+ "@openora/create": "{{coreVersion}}",
18
24
  "@openora/mcp": "latest",
19
- "@turbo/gen": "2.9.14",
20
- "@types/node": "25.9.0",
21
- "oxlint": "1.64.0",
22
- "rulesync": "8.25.0",
23
- "tsx": "4.22.2",
24
- "turbo": "2.9.14",
25
- "typescript": "6.0.3"
25
+ "@turbo/gen": "{{turboVersion}}",
26
+ "@types/node": "{{typesNodeVersion}}",
27
+ "oxlint": "{{oxlintVersion}}",
28
+ "rulesync": "{{rulesyncVersion}}",
29
+ "tsx": "{{tsxVersion}}",
30
+ "turbo": "{{turboVersion}}",
31
+ "typescript": "{{typescriptVersion}}"
26
32
  },
27
33
  "engines": {
28
34
  "node": ">=22.0.0",
29
35
  "pnpm": ">=10.0.0"
30
36
  },
31
- "packageManager": "pnpm@11.5.2"
37
+ "packageManager": "{{packageManager}}"
32
38
  }
@@ -8,13 +8,13 @@
8
8
  // AGENTS.md stays a clean human brief (no simulated-command boilerplate).
9
9
  // Hooks + permissions are NOT managed here - .claude/settings.json is hand-maintained
10
10
  // (it holds the guard hooks), so rulesync never clobbers it.
11
- // codexcli: rules (AGENTS.md) + mcp (.codex/config.toml) only - its commands/
12
- // subagents are simulation-only (kept off) and it has no hooks system.
11
+ // codexcli: rules (AGENTS.md), mcp (.codex/config.toml) and hooks (.codex/hooks.json, so
12
+ // the guards judge its apply_patch edits) - its commands/subagents are simulation-only (kept off).
13
13
  "targets": {
14
14
  "agentsmd": ["rules"],
15
15
  "claudecode": ["rules", "mcp", "subagents", "commands", "skills"],
16
16
  "copilot": ["rules", "mcp", "subagents", "commands", "hooks"],
17
- "codexcli": ["rules", "mcp"],
17
+ "codexcli": ["rules", "mcp", "hooks"],
18
18
  },
19
19
 
20
20
  // rulesync emits Copilot agents/prompts natively - no simulation needed.
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ // A git worktree of the sibling OSS checkout - the only place an agent in this repo may change
3
+ // OSS code (the guard-core hook allows {{ossFromRoot}}/.worktrees/ and denies the rest of the
4
+ // checkout). Same command for every agent and every human, so a paired change looks the same
5
+ // on every machine.
6
+ //
7
+ // pnpm oss:worktree <branch> create or reuse the worktree, install its deps
8
+ // pnpm oss:worktree <branch> --link ...then build it and point `pnpm link:oss` at it
9
+ // pnpm oss:worktree <branch> --remove remove it; relinks the main checkout if it was linked
10
+ //
11
+ // Use the same branch name as this repo's branch: that is how skills pair the two requests.
12
+ import { execFileSync } from 'node:child_process';
13
+ import { existsSync, readFileSync } from 'node:fs';
14
+ import { join, relative, resolve } from 'node:path';
15
+ // oxlint-disable no-console
16
+
17
+ // This repo is often checked out as a git worktree, whose `.git` is a file pointing at
18
+ // <main>/.git/worktrees/<name>. The sibling OSS checkout sits next to the MAIN checkout, so
19
+ // resolve from there or every path below lands inside the worktree.
20
+ const mainCheckoutOf = (dir) => {
21
+ try {
22
+ const pointer = readFileSync(join(dir, '.git'), 'utf8');
23
+ const gitdir = pointer.match(/^gitdir:\s*(.*[/\\]worktrees[/\\][^/\\]+?)\s*$/m)?.[1];
24
+ return gitdir ? resolve(dir, gitdir, '..', '..', '..') : dir;
25
+ } catch {
26
+ return dir; // `.git` is a directory (the main checkout) or missing
27
+ }
28
+ };
29
+
30
+ const OSS = resolve(mainCheckoutOf(process.cwd()), '{{ossFromRoot}}');
31
+ const USAGE = 'usage: pnpm oss:worktree <branch> [--link | --remove]';
32
+
33
+ const die = (message) => {
34
+ console.error(`oss:worktree - ${message}`);
35
+ process.exit(1);
36
+ };
37
+
38
+ const [branch, flag] = process.argv.slice(2);
39
+ if (!branch || branch.startsWith('--') || (flag && !['--link', '--remove'].includes(flag))) {
40
+ die(USAGE);
41
+ }
42
+ if (!existsSync(join(OSS, '.git'))) {
43
+ die(`no OSS checkout at ${OSS} - clone it next to this repo first.`);
44
+ }
45
+
46
+ const worktree = join(OSS, '.worktrees', branch.replaceAll('/', '+'));
47
+ const worktreeFromHere = relative(process.cwd(), worktree);
48
+ // A fresh worktree has no node_modules yet, so the checkout's own post-checkout hook would run
49
+ // against tools it cannot resolve and fail the command. The install below does that setup.
50
+ const GIT = ['-C', OSS, '-c', 'core.hooksPath=/dev/null'];
51
+ const git = (...args) =>
52
+ execFileSync('git', [...GIT, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] }).trim();
53
+ const gitOrEmpty = (...args) => {
54
+ try {
55
+ return execFileSync('git', [...GIT, ...args], { encoding: 'utf8', stdio: 'pipe' }).trim();
56
+ } catch {
57
+ return '';
58
+ }
59
+ };
60
+ const run = (cmd, args) => execFileSync(cmd, args, { stdio: 'inherit' });
61
+ const hasLinkScript = () =>
62
+ Boolean(JSON.parse(readFileSync('package.json', 'utf8')).scripts?.['link:oss']);
63
+ const isLinkedTo = (dir) =>
64
+ existsSync('pnpm-workspace.yaml') &&
65
+ readFileSync('pnpm-workspace.yaml', 'utf8').includes(`link:${relative(process.cwd(), dir)}/`);
66
+
67
+ if (flag === '--remove') {
68
+ const wasLinked = isLinkedTo(worktree);
69
+ if (existsSync(worktree)) {
70
+ try {
71
+ git('worktree', 'remove', worktree);
72
+ } catch {
73
+ die(`${worktreeFromHere} has uncommitted changes - commit or discard them, then rerun.`);
74
+ }
75
+ }
76
+ if (wasLinked && hasLinkScript()) {
77
+ run('pnpm', ['link:oss']);
78
+ }
79
+ console.log(`removed ${worktreeFromHere}`);
80
+ process.exit(0);
81
+ }
82
+
83
+ if (!existsSync(worktree)) {
84
+ git('fetch', 'origin', '--quiet');
85
+ const base = gitOrEmpty('rev-parse', '--abbrev-ref', 'origin/HEAD') || 'origin/dev';
86
+ if (gitOrEmpty('rev-parse', '--verify', '--quiet', `refs/heads/${branch}`)) {
87
+ git('worktree', 'add', worktree, branch);
88
+ } else if (gitOrEmpty('rev-parse', '--verify', '--quiet', `refs/remotes/origin/${branch}`)) {
89
+ git('worktree', 'add', '--track', '-b', branch, worktree, `origin/${branch}`);
90
+ } else {
91
+ git('worktree', 'add', '--no-track', '-b', branch, worktree, base);
92
+ }
93
+ run('pnpm', ['-C', worktree, 'install', '--frozen-lockfile']);
94
+ }
95
+
96
+ if (flag === '--link') {
97
+ if (!hasLinkScript()) {
98
+ die('this repo has no `link:oss` script - link the worktree through pnpm overrides by hand.');
99
+ }
100
+ // @openora/* resolve from dist/, so a linked worktree works only once it is built.
101
+ run('pnpm', ['-C', worktree, 'build']);
102
+ run('pnpm', ['link:oss', worktreeFromHere]);
103
+ }
104
+
105
+ console.log(worktreeFromHere);