@erclx/aitk 0.89.0 → 0.89.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aitk",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "0.89.0",
4
+ "version": "0.89.1",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -196,6 +196,8 @@ properly and stop when you cannot.
196
196
  Serialize any track that touches a shared wiring seam with another in flight.
197
197
  Merge the branch with the smallest shared-file footprint first, and merge a
198
198
  branch touching `CLAUDE.md`, a Claude context entry, or a regenerated `index.md`
199
- last. Have every sibling rebase on the new `main` before the next merge. Assign
200
- a distinct port per track when two workers run a server, since each session
201
- spawns its own process.
199
+ last. Have every sibling rebase on the new `main` before the next merge. Two
200
+ workers running a server take a port apiece without being told to, since a
201
+ stack derives it from the worktree it runs in through `scripts/worktree-port.sh`.
202
+ Read that value rather than assigning one, and set `WORKTREE_PORT_OFFSET` by
203
+ hand only when two worktrees derive the same offset.
@@ -11,6 +11,10 @@ Without this skill, the user names the worktree by hand, and a name matching no
11
11
 
12
12
  Entry also writes the bare flag into the shared config, which strands the main worktree. Every command run there fails while the files sit untouched on disk, and the linked worktree keeps working, so nothing surfaces until the operator returns to the main checkout and finds the repository broken. A rename onto a branch that already exists is the third failure, and it is the one that destroys work rather than blocking it.
13
13
 
14
+ The tree the entry hands over is also not one the session can run. Dependencies live in a folder git ignores and never shares between working directories, so a fresh worktree arrives with none, and nothing on the entry path says so. The session finds out from whichever command needs them first, and the message it gets names a missing module rather than an empty working directory.
15
+
16
+ A stack that derives its ports from the working directory has the same shape. The number is correct and invisible, and `claude-orchestrate` sends a reader here to read it rather than assign one, so the entry that knows the working directory is the surface that owes it.
17
+
14
18
  ## Must
15
19
 
16
20
  - Derive the name from the plan matched to the current branch, falling through the ordered sources rather than picking
@@ -21,9 +25,13 @@ Entry also writes the bare flag into the shared config, which strands the main w
21
25
  - Test the target branch and the target directory before entering, so a stop costs no worktree
22
26
  - Read the bare flag before writing it, and repair it on both sides of entry
23
27
  - Announce the repair only when a write actually happened
28
+ - Report on one line whether the entered worktree carries its dependencies, naming the command that installs them when it does not
29
+ - Report the same way when the project declares no package manifest, since silence there reads as a passing check
30
+ - Report the port offset this worktree derives, and say so when the project installs no derivation
24
31
 
25
32
  ## Must not
26
33
 
34
+ - Install dependencies on the session's behalf. Entering a worktree to read is as common as entering one to run, and an install is slow and needs a network.
27
35
  - Pick between plans when more than one could match. Ask.
28
36
  - Enter on a name inferred from session context without confirmation
29
37
  - Delete or overwrite a branch or a worktree directory that already carries the target name
@@ -96,4 +96,26 @@ git config core.bare false
96
96
 
97
97
  The flag is not set on every entry, so read before writing and announce only when the write happened. Tracked upstream as `anthropics/claude-code#58345`, closed as not planned, so the repair stays until the tool changes.
98
98
 
99
+ ## Step 6: report whether the tree can run
100
+
101
+ A linked worktree is a second working directory over one repository, and every ecosystem installs its dependencies into a folder git ignores. Nothing copies that folder across, so a fresh worktree arrives without it and the session learns as much from whichever command needs it first, which reports a missing module rather than an empty working directory.
102
+
103
+ Report the state on one line. Do not install. Entering a worktree to read is as common as entering one to run, and an install is slow, needs a network, and picks an ecosystem on the session's behalf.
104
+
105
+ Read the worktree root and emit the first line that matches:
106
+
107
+ - `package.json` present, `node_modules/` missing: `Dependencies are not installed. Run <install> before any build, test, or server command.` Take `<install>` from the lockfile beside the manifest, and use `bun install` when no lockfile names one.
108
+ - A `pyproject.toml` or `requirements.txt` present, `.venv/` missing: `No virtual environment. Create and populate one before running anything.`
109
+ - A manifest present with its folder alongside it: `Dependencies are installed.`
110
+ - No package manifest of either kind: `No package manifest, so there is nothing to install.`
111
+
112
+ The last line is what keeps the step honest on a stack this skill cannot read. Entry is not stack-aware, and silence is indistinguishable from a check that passed.
113
+
114
+ Then report the port this worktree derives, on a second line:
115
+
116
+ - `scripts/worktree-port.sh` present: run `bash scripts/worktree-port.sh` and emit `Port offset <n>. Every served port adds it to the stack default.`
117
+ - Absent: `No port derivation installed, so every served port is the stack default.`
118
+
119
+ The offset is what `claude-orchestrate` sends a reader here to read rather than assign, and what an operator overrides through `WORKTREE_PORT_OFFSET` when two worktrees derive the same value. Deriving it correctly and printing it nowhere leaves both instructions naming a number no surface emits.
120
+
99
121
  Do not invoke `ExitWorktree` from this skill. Exit is the user's call.
@@ -125,8 +125,10 @@ at two or three worker tracks and split them across the stack so they do not
125
125
  collide on the same files.
126
126
 
127
127
  Unit checks run freely in many worktrees at once.
128
- Only fixed-port work (a dev server, an end-to-end run, a screenshot) and
129
- singleton resources (one local model server, one GPU) serialize. See
128
+ A dev server, an end-to-end run, and a screenshot run alongside each other on a
129
+ web stack, since every worktree derives its own port. Singleton resources (one
130
+ local model server, one GPU) still serialize, as does any port a stack fixes by
131
+ hand. See
130
132
  [Claude Code and git worktrees](../wiki/claude/claude-worktrees.md) for merge order and the
131
133
  port-collision detail.
132
134
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.89.0",
4
+ "version": "0.89.1",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -3,9 +3,14 @@ import tailwindcss from '@tailwindcss/vite'
3
3
  import { defineConfig } from 'astro/config'
4
4
  import path from 'path'
5
5
 
6
+ const portOffset = Number(process.env.WORKTREE_PORT_OFFSET) || 0
7
+
6
8
  export default defineConfig({
7
9
  integrations: [react()],
8
10
  site: process.env.ASTRO_SITE,
11
+ server: {
12
+ port: 4321 + portOffset,
13
+ },
9
14
  vite: {
10
15
  plugins: [tailwindcss()],
11
16
  resolve: {
@@ -13,5 +18,11 @@ export default defineConfig({
13
18
  '@': path.resolve('./src'),
14
19
  },
15
20
  },
21
+ server: {
22
+ strictPort: true,
23
+ },
24
+ preview: {
25
+ strictPort: true,
26
+ },
16
27
  },
17
28
  })
@@ -1,6 +1,7 @@
1
1
  import { defineConfig, devices } from '@playwright/test'
2
2
 
3
3
  const isCI = !!process.env.CI
4
+ const baseURL = `http://localhost:${4321 + (Number(process.env.WORKTREE_PORT_OFFSET) || 0)}`
4
5
 
5
6
  export default defineConfig({
6
7
  testDir: 'e2e',
@@ -10,7 +11,7 @@ export default defineConfig({
10
11
  reporter: isCI ? 'list' : 'html',
11
12
  use: {
12
13
  trace: 'on-first-retry',
13
- baseURL: 'http://localhost:4321',
14
+ baseURL,
14
15
  },
15
16
  projects: [
16
17
  { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
@@ -19,7 +20,7 @@ export default defineConfig({
19
20
  ],
20
21
  webServer: {
21
22
  command: 'bun run build && bun run preview',
22
- url: 'http://localhost:4321',
23
- reuseExistingServer: !isCI,
23
+ url: baseURL,
24
+ reuseExistingServer: false,
24
25
  },
25
26
  })
@@ -15,15 +15,17 @@ packages = [
15
15
  ]
16
16
 
17
17
  [scripts]
18
- "dev" = "astro dev"
18
+ "dev" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) astro dev"
19
19
  "build" = "astro check && astro build"
20
- "preview" = "astro preview"
20
+ "preview" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) astro preview"
21
21
  "astro" = "astro"
22
22
  "typecheck" = "astro check"
23
23
  "setup" = "./scripts/setup.sh"
24
24
 
25
25
  [scripts.override]
26
- "screenshot" = "PREVIEW_PORT=4321 bash scripts/screenshot.sh"
26
+ "screenshot" = "PREVIEW_PORT=$(bash scripts/worktree-port.sh 4321) bash scripts/screenshot.sh"
27
+ "dev" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) astro dev"
28
+ "preview" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) astro preview"
27
29
 
28
30
  [gitignore]
29
31
  "# Astro" = [".astro/"]
@@ -17,9 +17,9 @@ The astro stack covers Astro + TypeScript projects: content sites, marketing sit
17
17
 
18
18
  ## What ships as golden configs
19
19
 
20
- - `astro.config.mjs`: `@astrojs/react` integration, `@tailwindcss/vite` in `vite.plugins`, `@/` path alias via `vite.resolve.alias`, `ASTRO_SITE` env for the `site` field.
20
+ - `astro.config.mjs`: `@astrojs/react` integration, `@tailwindcss/vite` in `vite.plugins`, `@/` path alias via `vite.resolve.alias`, `ASTRO_SITE` env for the `site` field. Port `4321` plus `WORKTREE_PORT_OFFSET` at `server.port`, with `strictPort` under `vite.server` and `vite.preview`. Astro merges the user's `vite` block into the config backing both its dev and its static preview server, and feeds `server.port` through as the preview port, so the port sits at the top level while the bind guarantee sits under `vite`.
21
21
  - `vitest.config.ts`: uses `getViteConfig` from `astro/config` (not `mergeConfig`). jsdom, globals, setup file, `passWithNoTests: true`, v8 coverage, `**/*.astro` in coverage excludes.
22
- - `playwright.config.ts`: all browsers, `webServer` runs `bun run build && bun run preview` on port 4321. Astro's dev/prod gap is wide (MDX, island hydration, asset optimization), so E2E always tests the built `dist/`.
22
+ - `playwright.config.ts`: all browsers, `webServer` runs `bun run build && bun run preview` on port `4321` plus `WORKTREE_PORT_OFFSET`, `reuseExistingServer: false`. Astro's dev/prod gap is wide (MDX, island hydration, asset optimization), so E2E always tests the built `dist/`.
23
23
  - `tsconfig.json`: extends `astro/tsconfigs/strict`, adds `skipLibCheck`, `vitest/globals` and `@testing-library/jest-dom` in types, `@/` paths.
24
24
  - `eslint.config.js`: overrides the web layer. Adds `eslint-plugin-astro` (`.astro` parser via `astro-eslint-parser`). React-hooks scoped to `.jsx`/`.tsx` only (`.astro` is not React). `src/pages/**` exempt from filename and folder naming conventions because Astro's file-based routing ties names to URL segments.
25
25
 
@@ -49,7 +49,7 @@ Add `prettier-plugin-astro` first in plugins, then `prettier-plugin-tailwindcss`
49
49
 
50
50
  Append to the `## Scripts` table:
51
51
 
52
- | `bun run dev` | Start the Astro dev server on port 4321. |
52
+ | `bun run dev` | Start the Astro dev server on port 4321, plus this worktree's port offset. |
53
53
  | `bun run build` | Run `astro check` then build the static output. |
54
54
  | `bun run preview` | Serve the built site locally. |
55
55
  | `bun run astro` | Expose the Astro CLI. |
@@ -1,6 +1,7 @@
1
1
  import { defineConfig, devices } from '@playwright/test'
2
2
 
3
3
  const isCI = !!process.env.CI
4
+ const baseURL = `http://localhost:${5173 + (Number(process.env.WORKTREE_PORT_OFFSET) || 0)}`
4
5
 
5
6
  export default defineConfig({
6
7
  testDir: 'e2e',
@@ -10,7 +11,7 @@ export default defineConfig({
10
11
  reporter: isCI ? 'list' : 'html',
11
12
  use: {
12
13
  trace: 'on-first-retry',
13
- baseURL: 'http://localhost:5173',
14
+ baseURL,
14
15
  },
15
16
  projects: [
16
17
  { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
@@ -19,7 +20,7 @@ export default defineConfig({
19
20
  ],
20
21
  webServer: {
21
22
  command: 'bun run dev',
22
- url: 'http://localhost:5173',
23
- reuseExistingServer: !isCI,
23
+ url: baseURL,
24
+ reuseExistingServer: false,
24
25
  },
25
26
  })
@@ -3,6 +3,8 @@ import react from '@vitejs/plugin-react'
3
3
  import path from 'path'
4
4
  import { defineConfig } from 'vite'
5
5
 
6
+ const portOffset = Number(process.env.WORKTREE_PORT_OFFSET) || 0
7
+
6
8
  export default defineConfig({
7
9
  plugins: [react(), tailwindcss()],
8
10
  resolve: {
@@ -11,4 +13,12 @@ export default defineConfig({
11
13
  },
12
14
  },
13
15
  base: process.env.VITE_BASE_URL ?? '/',
16
+ server: {
17
+ port: 5173 + portOffset,
18
+ strictPort: true,
19
+ },
20
+ preview: {
21
+ port: 4173 + portOffset,
22
+ strictPort: true,
23
+ },
14
24
  })
@@ -11,11 +11,13 @@ packages = [
11
11
  ]
12
12
 
13
13
  [scripts]
14
- "dev" = "vite"
14
+ "dev" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) vite"
15
15
  "build" = "tsc --noEmit && vite build"
16
- "preview" = "vite preview"
16
+ "preview" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) vite preview"
17
17
  "typecheck" = "tsc --noEmit"
18
18
  "setup" = "./scripts/setup.sh"
19
19
 
20
20
  [scripts.override]
21
21
  "build" = "tsc --noEmit && vite build"
22
+ "dev" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) vite"
23
+ "preview" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) vite preview"
@@ -16,16 +16,16 @@ The vite-react stack covers Vite + React + TypeScript projects: web apps and Chr
16
16
 
17
17
  ## What ships as golden configs
18
18
 
19
- - `vite.config.ts`: `@vitejs/plugin-react`, `@tailwindcss/vite`, `@` path alias to `./src`, `VITE_BASE_URL` env for base path.
19
+ - `vite.config.ts`: `@vitejs/plugin-react`, `@tailwindcss/vite`, `@` path alias to `./src`, `VITE_BASE_URL` env for base path. Dev port `5173` and preview port `4173`, each plus `WORKTREE_PORT_OFFSET`, both with `strictPort`.
20
20
  - `vitest.config.ts`: merges from `vite.config.ts`, jsdom, globals, setup file, `passWithNoTests: true`, v8 coverage.
21
- - `playwright.config.ts`: all browsers, `webServer` on `bun run dev` at port 5173, trace under `use`.
21
+ - `playwright.config.ts`: all browsers, `webServer` on `bun run dev` at port `5173` plus `WORKTREE_PORT_OFFSET`, `reuseExistingServer: false`, trace under `use`.
22
22
  - `tsconfig.json`: unified, `noEmit: true`, `skipLibCheck: true`, `@/` paths, `vitest/globals` and `@testing-library/jest-dom` in types.
23
23
 
24
24
  ## Chrome extension variant
25
25
 
26
26
  When scaffolding a Chrome extension, override the installed golden configs:
27
27
 
28
- - `vite.config.ts`: use `crx({ manifest })` and `zip()` from `@crxjs/vite-plugin` instead of `react()` alone. Set `server.port: 5173`, `server.strictPort: true`, `server.hmr.clientPort: 5173`, and `chrome-extension://` in CORS origins. Drop `VITE_BASE_URL`.
28
+ - `vite.config.ts`: use `crx({ manifest })` and `zip()` from `@crxjs/vite-plugin` instead of `react()` alone. Keep the derived `server.port` and `server.strictPort: true`, set `server.hmr.clientPort` to the same derived value, and add `chrome-extension://` to CORS origins. Drop `VITE_BASE_URL`.
29
29
  - `vitest.config.ts`: use a standalone `defineConfig` (no `mergeConfig`). crxjs plugin breaks Vitest. Declare `@vitejs/plugin-react` and `@tailwindcss/vite` directly. Add `**/release/**` to excludes and `manifest.config.ts`, `**/*.d.ts` to coverage excludes.
30
30
  - `playwright.config.ts`: chromium-only (Firefox and WebKit cannot run extensions). Bundled `chromium` channel. No `baseURL` or `webServer`. Tests load the built extension directly from `dist/`.
31
31
  - `e2e/fixtures.ts`: extend Playwright base `test` with `context` (persistent context loading the extension from `dist/`) and `extensionId` (extracted from service worker URL). Rename `use` to `apply` to avoid the React hooks ESLint rule. `waitForEvent('serviceworker')` blocks until the MV3 service worker registers.
@@ -45,7 +45,7 @@ When scaffolding a Chrome extension, override the installed golden configs:
45
45
 
46
46
  Append to the `## Scripts` table:
47
47
 
48
- | `bun run dev` | Start the Vite dev server on port 5173. |
48
+ | `bun run dev` | Start the Vite dev server on port 5173, plus this worktree's port offset. |
49
49
  | `bun run build` | Typecheck then build the production bundle. |
50
50
  | `bun run preview` | Serve the built bundle locally. |
51
51
  | `bun run typecheck` | Run `tsc --noEmit`. |
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Prints a port for this working directory: the base itself in a normal
5
+ # checkout, and the base plus a per-worktree offset in a linked git worktree,
6
+ # so two worktrees of one repository never serve on one port.
7
+
8
+ base="${1:-0}"
9
+ band=50
10
+
11
+ offset() {
12
+ if [[ -n "${WORKTREE_PORT_OFFSET:-}" ]]; then
13
+ echo "$WORKTREE_PORT_OFFSET"
14
+ return
15
+ fi
16
+
17
+ local git_dir common_dir name
18
+ git_dir=$(git rev-parse --git-dir 2>/dev/null) || {
19
+ echo 0
20
+ return
21
+ }
22
+ common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || {
23
+ echo 0
24
+ return
25
+ }
26
+
27
+ if [[ "$(cd "$git_dir" && pwd -P)" == "$(cd "$common_dir" && pwd -P)" ]]; then
28
+ echo 0
29
+ return
30
+ fi
31
+
32
+ name=$(basename "$(git rev-parse --show-toplevel)")
33
+ echo $(($(printf '%s' "$name" | cksum | cut -d' ' -f1) % band + 1))
34
+ }
35
+
36
+ echo $((base + $(offset)))
@@ -42,13 +42,13 @@ packages = [
42
42
  "test:run" = "vitest run --reporter=verbose"
43
43
  "test:ui" = "vitest --ui"
44
44
  "test:coverage" = "vitest run --coverage"
45
- "test:e2e" = "playwright test"
46
- "test:e2e:ui" = "playwright test --ui"
45
+ "test:e2e" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) playwright test"
46
+ "test:e2e:ui" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) playwright test --ui"
47
47
  "test:e2e:report" = "playwright show-report"
48
48
  "check:full" = "./scripts/verify.sh && bun run test:e2e"
49
49
 
50
50
  [scripts.override]
51
- "screenshot" = "bash scripts/screenshot.sh"
51
+ "screenshot" = "PREVIEW_PORT=$(bash scripts/worktree-port.sh 4173) bash scripts/screenshot.sh"
52
52
 
53
53
  [gitignore]
54
54
  "# Build" = ["dist/"]
@@ -16,6 +16,7 @@ Golden config files live in `tooling/web/configs/` and are copied into the targe
16
16
  - `.vscode/extensions.json` and `.vscode/settings.json`: editor wiring for ESLint, Tailwind, Playwright, Vitest.
17
17
  - `.github/workflows/verify.yml`: `static-checks`, `unit-tests`, `build-verify`, and `e2e-tests` jobs.
18
18
  - `scripts/verify.sh`: extends base verify with typecheck, lint, unit tests, and build in the full order.
19
+ - `scripts/worktree-port.sh`: prints a base port plus this working directory's offset. Called with no argument it prints the offset alone.
19
20
 
20
21
  ## What stays in per-stack adapters
21
22
 
@@ -32,6 +33,17 @@ Framework glue lives in `tooling/vite-react/configs/` or `tooling/astro/configs/
32
33
  - Path alias `@` maps to `./src` in both tsconfig and the framework's build config.
33
34
  - Tsconfig is unified at root with `noEmit: true` in Vite stacks. Astro uses the scaffold default from `@astrojs/check`.
34
35
 
36
+ ## Ports
37
+
38
+ Two worktrees of one repository run the same stack, so a fixed port makes the second one attach to the first.
39
+
40
+ - Derive every served port from `scripts/worktree-port.sh`. Never write a port literal into a script string.
41
+ - Read `WORKTREE_PORT_OFFSET` in a config and add it to the stack's default port. Unset yields the default, so a plain clone keeps the port it has always served on.
42
+ - Draw the offset from a band of 50, hashed from the worktree folder name. Two worktrees can hash to one offset, so set `WORKTREE_PORT_OFFSET` by hand to break a tie.
43
+ - Force-replace `dev` and `preview` through `[scripts.override]`. Both stacks' scaffolds define those keys, and a plain `[scripts]` entry never replaces a key the scaffold already wrote.
44
+ - Set `strictPort` on every dev and preview server. A server that walks to the next free port serves where nothing is looking for it.
45
+ - Set Playwright `reuseExistingServer: false`. Reuse attaches to whatever answers on the port, which reports a pass against another branch's code and prints nothing to say so.
46
+
35
47
  ## Anti-patterns
36
48
 
37
49
  Sticky negative knowledge. Do not relearn.