@joshuafolkken/kit 1.929.0 → 1.947.0

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 (78) hide show
  1. package/.claude/skills/workflow-commands/background-commands.md +6 -0
  2. package/.claude/skills/workflow-commands/backlogrun-child.md +1 -1
  3. package/.claude/skills/workflow-commands/backlogrun-lanes.md +4 -3
  4. package/.claude/skills/workflow-commands/observation-filing.md +3 -3
  5. package/.claude/skills/workflow-commands/prerequisite.md +1 -1
  6. package/.github/workflows/ci.yml +26 -8
  7. package/.github/workflows/pr-classification.yml +76 -0
  8. package/README.md +47 -22
  9. package/dist/hooks/batch-guard.js +4 -4
  10. package/dist/hooks/{chunk-GQ3EVWHY.js → chunk-3FWV6CFC.js} +113 -19
  11. package/dist/hooks/{chunk-2PGRQ62K.js → chunk-EYFJ74MJ.js} +11 -6
  12. package/dist/hooks/{chunk-YO43HEPY.js → chunk-GIX5SAAB.js} +237 -235
  13. package/dist/hooks/{chunk-LCJZE42I.js → chunk-IA7WC4NS.js} +1 -1
  14. package/dist/hooks/{chunk-BNFG2IFK.js → chunk-IBFC25IX.js} +4 -4
  15. package/dist/hooks/{chunk-I3JP3D4A.js → chunk-M4U4YNRE.js} +3 -3
  16. package/dist/hooks/{chunk-NJZSO7BK.js → chunk-NKVLBB65.js} +3 -3
  17. package/dist/hooks/{chunk-SXPTRFPG.js → chunk-O3PEDF3M.js} +1 -1
  18. package/dist/hooks/{chunk-7XQ6TK3R.js → chunk-OM5VDM6F.js} +2 -2
  19. package/dist/hooks/{chunk-ROULPSQC.js → chunk-V7RH56HU.js} +2 -2
  20. package/dist/hooks/codex-hook-adapter.js +10 -10
  21. package/dist/hooks/duplicate-read-guard.js +5 -5
  22. package/dist/hooks/format-edited.js +2 -2
  23. package/dist/hooks/investigation-guard.js +5 -5
  24. package/dist/hooks/pretool-guard.js +9 -9
  25. package/dist/hooks/stop-guard.js +9 -3
  26. package/dist/josh.js +11 -6
  27. package/package.json +13 -1
  28. package/prompts/collaboration-workflow/issue-template.md +1 -1
  29. package/scripts/ci/pr-classification.ts +27 -6
  30. package/scripts/document/document-byte-budget.ts +1 -1
  31. package/scripts/gate/hook-gate-reuse.ts +1 -1
  32. package/scripts/gate/project-checks.ts +45 -15
  33. package/scripts/gate/type-check-command.ts +43 -0
  34. package/scripts/git/constants.ts +4 -0
  35. package/scripts/git/git-command.ts +11 -5
  36. package/scripts/git/git-gh-pr-rollup.ts +56 -16
  37. package/scripts/git/git-staging.ts +6 -4
  38. package/scripts/git/git-stash.ts +73 -1
  39. package/scripts/git/stash-orphans.ts +24 -2
  40. package/scripts/git/stash-sweep-lock.ts +141 -0
  41. package/scripts/hooks/stop-guard.ts +2 -0
  42. package/scripts/init/init-actions.ts +3 -2
  43. package/scripts/init/init-logic-migrate.ts +10 -1
  44. package/scripts/init/init-logic-templates.ts +27 -7
  45. package/scripts/init/init-logic-workspace.ts +52 -1
  46. package/scripts/init/init-logic.ts +6 -1
  47. package/scripts/init/init.ts +60 -19
  48. package/scripts/issue/issue-merged.ts +75 -0
  49. package/scripts/issue/measure-rerun-cli.ts +2 -2
  50. package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +10 -0
  51. package/scripts/josh/josh-command-map.ts +1 -0
  52. package/scripts/josh/josh-command-types.ts +0 -3
  53. package/scripts/josh/josh-commands-ai.ts +6 -0
  54. package/scripts/josh/josh-commands-development.ts +5 -5
  55. package/scripts/lint/lint-parallel.ts +19 -18
  56. package/scripts/observations/observation-ledger-home.ts +22 -2
  57. package/scripts/observations/observation-ledger-line.ts +11 -0
  58. package/scripts/observations/observation-ledger-migrate.ts +146 -0
  59. package/scripts/observations/observation-ledger.ts +51 -3
  60. package/scripts/observations/observations-flush.ts +38 -7
  61. package/scripts/review/review-finding-ledger.ts +1 -1
  62. package/scripts/review/review-record-cli.ts +1 -7
  63. package/scripts/rules/delivered-rules.ts +10 -0
  64. package/scripts/rules/lane-background.ts +111 -0
  65. package/scripts/rules/stop-rules.ts +15 -2
  66. package/scripts/run/run-carry-flush.ts +2 -7
  67. package/scripts/run/run-hold-cli.ts +10 -2
  68. package/scripts/run/run-tidy-cli.ts +50 -0
  69. package/scripts/run/run-tidy-lanes.ts +110 -0
  70. package/scripts/run/run-tidy-stashes.ts +160 -0
  71. package/scripts/run/run-tidy.ts +101 -0
  72. package/scripts/safe-chain/ci-installer-pin.ts +90 -0
  73. package/scripts/safe-chain/preinstall-command.ts +39 -0
  74. package/scripts/sync/sync.ts +5 -2
  75. package/scripts/test/pilot-files.ts +0 -1
  76. package/scripts/version/preinstall-version-update.ts +2 -0
  77. package/templates/workflows/ci.yml +21 -7
  78. package/tsconfig/base.json +4 -1
@@ -126,5 +126,11 @@ act, so the run continues rather than stalling — the sanctioned boundary disti
126
126
  turn-end this section forbids. The boundary, its two commands and the resume verification are the
127
127
  `pre-gate-cut.md` skill document, its single source.
128
128
 
129
+ **A headless lane child (`claude -p`) is where "a background command re-invokes the run" does not
130
+ hold: its background Bash tasks are killed when its turn ends** (joshuafolkken/kit#2704). It hands the
131
+ gate-to-merge region to `pnpm josh ship --detach` instead (`chain-rule.md` step 0); `pnpm josh
132
+ rule:guard` refuses a backgrounded `josh gate` / `git` / `followup` there, and the `Stop` hook sends a
133
+ child with a task still running back to wait rather than to notify.
134
+
129
135
  The operational section above is the single source of the rule. `followup.md`, `chain-rule.md` and
130
136
  `backlogrun-progress.md` → "Progress while the run is quiet" route here for it rather than restating it.
@@ -125,7 +125,7 @@ brief hands them to the unit.
125
125
  discretionary observation has**: a child files `route:tier-a` and `route:interrupt` only, and the
126
126
  parent files what survives — `SKILL.md` → §2i, the single source. **What the parent does with the
127
127
  rest is append it, not drop it**: an observation that cannot cite the depth-0 work it blocked
128
- becomes one line in `docs/observations.md`, and a second line under the same key files it. **The
128
+ becomes one line in the observation ledger, and a second line under the same key files it. **The
129
129
  child never writes that file** — it cannot tell its observation from the sibling lane's.
130
130
  4. **Decisions taken and why**, where the decision was not already logged as an Issue comment.
131
131
  5. **What was left undone**, and under whose authority.
@@ -111,16 +111,17 @@ written.
111
111
  **Running unattended gets harder, not easier, and that is the honest trade.** Many lanes make the
112
112
  overlap between children real, and every overlap that becomes a conflict costs the child that loses the
113
113
  race a resolution, a re-run gate and a review ("Conflicts are not predicted" below), and parks it
114
- outright under that section's four conditions. The run finishes more work per hour **and** spends more
115
- of each child's budget on merge races. Do not report the first without the second.
114
+ outright under that section's four conditions. Report the throughput gain only with its merge-race
115
+ cost.
116
116
 
117
117
  ### Once per repository, before the first lane opens
118
118
 
119
119
  In the **primary checkout**, in this order, and never again per lane:
120
120
 
121
- 1. `git switch main && git pull` — every lane is branched from this ref.
121
+ 1. `git switch main && git pull` — every lane branches from this ref.
122
122
  2. `pnpm josh latest:scope`, and the update on `required` — "`josh latest` runs once per session" above.
123
123
  3. `pnpm josh lane:prune` — closes the lanes an interruption left registered without a work tree.
124
+ 4. `pnpm josh run:tidy` — sweeps merged lanes and stashes (as `run:hold` does).
124
125
 
125
126
  **`pnpm josh latest` is never run inside a lane, whatever `latest:scope` answers there** — the command
126
127
  enforces it: `latest:scope` answers `skip` in a lane and `pnpm josh latest` refuses outright, fronted
@@ -176,7 +176,7 @@ joshuafolkken/kit#1726 is the worked case: its own body says the depth test woul
176
176
  and it was filed anyway, because **discarding it was the only alternative on offer**
177
177
  (joshuafolkken/kit#1728).
178
178
 
179
- - **The destination is `docs/observations.md` in the repository the observation is about** — the same
179
+ - **The destination is `docs/maintainers/observations.md` in the repository the observation is about** — the same
180
180
  repository the Issue would have been filed into. **The count and the append are both run in that
181
181
  repository's checkout**, resolved the way §2c (`target-repository.md`) resolves any cross-repository target, and the file is
182
182
  created on the first append where that repository has none. **The subject decides, never the
@@ -230,7 +230,7 @@ watched. **A missing file is not a count of zero, though** — there `grep` exit
230
230
  number at all, so an empty answer means create the ledger, never that this is a first sighting.
231
231
 
232
232
  ```bash
233
- grep -c '^- k:<slug> |' <that repository's checkout>/docs/observations.md || true
233
+ grep -c '^- k:<slug> |' <that repository's checkout>/docs/maintainers/observations.md || true
234
234
  ```
235
235
 
236
236
  Free-text comparison is what the key exists to replace, so two lines that read alike under different
@@ -255,7 +255,7 @@ that is empty on every other machine**, and every sighting was a first one, whic
255
255
  joshuafolkken/kit#1728 created the ledger to end.
256
256
 
257
257
  - **An ordinary run never commits the ledger, and that is enforced rather than remembered.**
258
- `pnpm josh git` stages with `docs/observations.md` excluded, in the one staging step every entry
258
+ `pnpm josh git` stages with `docs/maintainers/observations.md` excluded, in the one staging step every entry
259
259
  point goes through (`scripts/git/git-staging.ts`), so a `fullrun` in the primary checkout **cannot**
260
260
  carry a ledger line into an Issue that has nothing to do with it. It is not a rule a run has to
261
261
  remember at the commit — a run that had to would be the run that forgets.
@@ -18,7 +18,7 @@ another is the failure this section exists to prevent:
18
18
  | A defect originating in **another package** | File the upstream Issue and **stop** — Tier A for a first-party target; a third-party one is Tier C, recorded and drafted rather than filed (`CLAUDE.md` → "Cross-package problems"; `prompts/collaboration-workflow/upstream-interrupt.md`) |
19
19
  | This Issue was really **several** (a split) | File the children and the epic and **stop** — except under `backlogrun`, whose authorization already covers a batch, so the children are filed and run through (`split-assessment.md`) |
20
20
  | Another Issue in **this** repository has to land first (**a prerequisite**) | This section |
21
- | Something worth filing that is **none of the three** (**an observation**) | File it **without asking** — Tier A for a first-party target — and **carry the run straight on**: nothing is stashed, nothing is parked (§2i). **A delegated child does not file here**, and a filing at depth 1 or deeper cites the depth-0 work it blocked; one that cannot cite it goes to `docs/observations.md` and is filed on its second sighting — all of them §2i's |
21
+ | Something worth filing that is **none of the three** (**an observation**) | File it **without asking** — Tier A for a first-party target — and **carry the run straight on**: nothing is stashed, nothing is parked (§2i). **A delegated child does not file here**, and a filing at depth 1 or deeper cites the depth-0 work it blocked; one that cannot cite it goes to `docs/maintainers/observations.md` and is filed on its second sighting — all of them §2i's |
22
22
 
23
23
  **File the prerequisite with the `route:tier-a` label**, so a Tier A filing made during implementation
24
24
  stays countable by filing route afterwards. **This paragraph belongs to the prerequisite row, not to
@@ -4,9 +4,13 @@ on:
4
4
  push:
5
5
  branches: [main]
6
6
 
7
+ # No `labeled` / `unlabeled`: a label added right after the pull request opens started a second
8
+ # run on the same commit, the concurrency block below cancelled one of the two, and a cancelled
9
+ # run left in the newer check suite blocked the merge (joshuafolkken/kit#2712). The one check that
10
+ # reads labels runs in `pr-classification.yml` instead.
7
11
  pull_request:
8
12
  branches: [main]
9
- types: [opened, synchronize, reopened, labeled, unlabeled]
13
+ types: [opened, synchronize, reopened]
10
14
 
11
15
  # A push run is grouped by the commit it carries, so a later push to main never cancels an earlier
12
16
  # one. Without the sha every push to main shares one group — `github.ref` is `refs/heads/main` for
@@ -23,6 +27,12 @@ concurrency:
23
27
  # set the JOSH_PLAYWRIGHT_BROWSERS repository variable to a space-separated list to widen it.
24
28
  env:
25
29
  JOSH_PLAYWRIGHT_BROWSERS: ${{ vars.JOSH_PLAYWRIGHT_BROWSERS || 'chromium' }}
30
+ # The safe-chain release every "Setup safe-chain" step installs, and the SHA-256 of that release's
31
+ # install-safe-chain.sh. The installer puts the safe-chain binary under ~/.safe-chain/bin and
32
+ # adds it to PATH beside the shims; `pnpm dlx … setup-ci` left only the shims, which then let
33
+ # every install through without a scan (joshuafolkken/kit#2711). `josh latest` moves both together.
34
+ SAFE_CHAIN_INSTALLER_VERSION: 1.5.20
35
+ SAFE_CHAIN_INSTALLER_SHA256: 0ad25efe15d1fa56105157a454d647223e78eb0c53d1f85e3d10afcd722e7bfd
26
36
 
27
37
  jobs:
28
38
  node-26-pnpm:
@@ -308,7 +318,11 @@ jobs:
308
318
  ${{ runner.os }}-cspell-cache-
309
319
 
310
320
  - name: Setup safe-chain
311
- run: pnpm dlx @aikidosec/safe-chain@1.5.20 setup-ci
321
+ run: |
322
+ installer="$RUNNER_TEMP/install-safe-chain.sh"
323
+ curl --proto '=https' -fsSL "https://github.com/AikidoSec/safe-chain/releases/download/$SAFE_CHAIN_INSTALLER_VERSION/install-safe-chain.sh" -o "$installer"
324
+ echo "$SAFE_CHAIN_INSTALLER_SHA256 $installer" | sha256sum -c -
325
+ sh "$installer" --ci
312
326
 
313
327
  - name: Install dependencies
314
328
  env:
@@ -352,10 +366,6 @@ jobs:
352
366
  - name: Run verification gate
353
367
  run: pnpm josh gate --verbose --no-unit
354
368
 
355
- - name: Check release classification
356
- if: github.event_name == 'pull_request'
357
- run: pnpm josh pr:classification
358
-
359
369
  # The other half of the gate, on a runner of its own. It carries the same setup steps as
360
370
  # `static-checks` rather than sharing them: a GitHub job is its own machine, so every job that
361
371
  # needs Node sets Node up, and this workflow already writes that preamble once per job. The
@@ -411,7 +421,11 @@ jobs:
411
421
  ${{ runner.os }}-pnpm-store-
412
422
 
413
423
  - name: Setup safe-chain
414
- run: pnpm dlx @aikidosec/safe-chain@1.5.20 setup-ci
424
+ run: |
425
+ installer="$RUNNER_TEMP/install-safe-chain.sh"
426
+ curl --proto '=https' -fsSL "https://github.com/AikidoSec/safe-chain/releases/download/$SAFE_CHAIN_INSTALLER_VERSION/install-safe-chain.sh" -o "$installer"
427
+ echo "$SAFE_CHAIN_INSTALLER_SHA256 $installer" | sha256sum -c -
428
+ sh "$installer" --ci
415
429
 
416
430
  - name: Install dependencies
417
431
  env:
@@ -623,7 +637,11 @@ jobs:
623
637
  ${{ runner.os }}-build-cache-e2e-
624
638
 
625
639
  - name: Setup safe-chain
626
- run: pnpm dlx @aikidosec/safe-chain@1.5.20 setup-ci
640
+ run: |
641
+ installer="$RUNNER_TEMP/install-safe-chain.sh"
642
+ curl --proto '=https' -fsSL "https://github.com/AikidoSec/safe-chain/releases/download/$SAFE_CHAIN_INSTALLER_VERSION/install-safe-chain.sh" -o "$installer"
643
+ echo "$SAFE_CHAIN_INSTALLER_SHA256 $installer" | sha256sum -c -
644
+ sh "$installer" --ci
627
645
 
628
646
  - name: Install dependencies
629
647
  env:
@@ -0,0 +1,76 @@
1
+ name: Release classification
2
+
3
+ # The release-classification check, on its own because it is the one check that reads labels
4
+ # (joshuafolkken/kit#2712). While it lived in CI, CI had to rerun on `labeled` / `unlabeled`; `josh pr`
5
+ # labels a pull request the moment it opens, so every pull request started two CI runs on one commit,
6
+ # and CI's per-ref concurrency cancelled one of them. GitHub judges a check name by its newest check
7
+ # suite, and which of the two runs sat in the newer suite was a race — when it was the cancelled one,
8
+ # the pull request stayed BLOCKED until someone reran it.
9
+ on:
10
+ pull_request:
11
+ branches: [main]
12
+ types: [opened, synchronize, reopened, labeled, unlabeled]
13
+
14
+ # Deliberately no `concurrency` block. Cancelling is exactly what left the blocking suite behind, and
15
+ # nothing here needs it: every run reads the pull request's labels as they stand when it runs, not as
16
+ # its event carried them, so whichever suite GitHub counts as newest holds a current verdict.
17
+
18
+ jobs:
19
+ classification:
20
+ name: Release classification
21
+ runs-on: ubuntu-latest
22
+ timeout-minutes: 5
23
+ permissions:
24
+ contents: read
25
+ packages: read
26
+ pull-requests: read
27
+ steps:
28
+ - name: Checkout code
29
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
30
+ with:
31
+ persist-credentials: false
32
+
33
+ - name: Setup Node.js
34
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
35
+ with:
36
+ node-version: '24'
37
+
38
+ - name: Configure GitHub Packages authentication for existing projects
39
+ run: printf '//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}\n' >> "$HOME/.npmrc"
40
+
41
+ - name: Resolve pnpm version
42
+ id: pnpm-version
43
+ run: |
44
+ version=$(node -p "const m = require('./package.json'); m.packageManager?.match(/^pnpm@([^+]+)/)?.[1] ?? (m.devEngines?.packageManager?.name === 'pnpm' ? m.devEngines.packageManager.version?.split('+')[0] : 'latest') ?? 'latest'")
45
+ test -n "$version"
46
+ echo "version=$version" >> "$GITHUB_OUTPUT"
47
+
48
+ - name: Setup pnpm
49
+ uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0
50
+ with:
51
+ version: ${{ steps.pnpm-version.outputs.version }}
52
+ install: false
53
+
54
+ - name: Resolve pnpm store path
55
+ run: echo "PNPM_STORE_PATH=$(pnpm store path)" >> "$GITHUB_ENV"
56
+
57
+ - name: Setup pnpm store cache
58
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
59
+ with:
60
+ path: ${{ env.PNPM_STORE_PATH }}
61
+ key: ${{ runner.os }}-pnpm-store-${{ hashFiles('pnpm-lock.yaml') }}
62
+ restore-keys: |
63
+ ${{ runner.os }}-pnpm-store-
64
+
65
+ - name: Setup safe-chain
66
+ run: pnpm dlx @aikidosec/safe-chain@1.5.20 setup-ci
67
+
68
+ - name: Install dependencies
69
+ env:
70
+ NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
71
+ run: pnpm install --frozen-lockfile --ignore-scripts
72
+
73
+ - name: Check release classification
74
+ env:
75
+ GH_TOKEN: ${{ github.token }}
76
+ run: pnpm josh pr:classification
package/README.md CHANGED
@@ -1,42 +1,67 @@
1
1
  # @joshuafolkken/kit
2
2
 
3
- Shared toolchain config and CLI for TypeScript projects — ESLint, Prettier, TypeScript, Lefthook, cspell, VS Code, and AI assistant files (CLAUDE.md, plus AGENTS.md / GEMINI.md pointing at it), wired together. It is framework-agnostic; the SvelteKit-specific layer lives in the separate [`@joshuafolkken/app-kit`](https://github.com/joshuafolkken/app-kit) package.
3
+ A development toolkit that sets up a repository for AI-assisted work with one command. `josh init` gives an AI assistant (Claude Code, Codex, Gemini, Cursor) the project's rules, and the `josh` CLI runs the checks and the Git / GitHub Issue workflow the same way in every project. You stop copying config between repositories and re-explaining conventions to the assistant.
4
4
 
5
- - **`josh` CLI** — lint, format, type-check, git workflow, versioning, and security auditing from any directory.
6
- - **Project config package** — ESLint / Prettier / tsconfig presets, prompts, and scripts consumed as a devDependency.
5
+ ## What your project gets
7
6
 
8
- ## Prerequisites
7
+ kit does not branch by language. It asks one question — does the project use Node tooling? — and picks a [profile](./docs/init.md#project-profiles):
9
8
 
10
- - [Node.js](https://nodejs.org/) 22.19.0 or later with [pnpm](https://pnpm.io/) — [getting-started.md](./docs/getting-started.md) covers installing both
11
- - [gh CLI](https://cli.github.com/) for `josh version` and existing GitHub Packages consumers. It is not required to install kit from public npm; see [authentication.md](./docs/authentication.md) for setup when needed.
9
+ | Your project | Profile | What you get |
10
+ | ---------------------------------------- | -------- | ----------------------------------------------------------------- |
11
+ | Node project (JavaScript / TypeScript) | `node` | The full set: ESLint, type check, unit and E2E tests, Git hooks |
12
+ | HTML / CSS (browser JavaScript included) | `static` | Formatting (Prettier), AI assistant files, Git / VS Code settings |
13
+ | Another language (Python, Rust, Go, …) | `static` | AI assistant files and Git / VS Code settings only |
14
+
15
+ kit adds no linter or test runner for languages other than JavaScript and TypeScript; in a `static` project, `josh` skips each check that has nothing to run and says why.
12
16
 
13
17
  ## Quick start
14
18
 
19
+ In your project directory:
20
+
15
21
  ```bash
16
- pnpm add -g @joshuafolkken/kit # install the josh CLI globally
17
- josh help
22
+ pnpm add -D --allow-build=esbuild @joshuafolkken/kit
23
+ pnpm exec josh init
24
+ pnpm install
25
+ pnpm josh format
26
+ pnpm josh gate
18
27
  ```
19
28
 
20
- Using the kit inside a project? See [docs/package.md](./docs/package.md).
29
+ It needs [Node.js](https://nodejs.org/) 22.19.0 or later with [pnpm](https://pnpm.io/), even for a `static` project; [getting-started.md](./docs/getting-started.md) installs both. The global `josh` command ([cli.md](./docs/cli.md)) and the [gh CLI](https://cli.github.com/) for `josh version` and the GitHub Issue workflow are optional.
21
30
 
22
31
  ## Documentation
23
32
 
24
- | Guide | What it covers |
25
- | ----------------------------------------------- | -------------------------------------------------------------------------------- |
26
- | [why.md](./docs/why.md) | Why kit exists — the AI-development problems it solves, for practitioners |
27
- | [getting-started.md](./docs/getting-started.md) | First install for an `index.html` site or a non-Node project, without Git |
28
- | [authentication.md](./docs/authentication.md) | GitHub Packages auth for existing projects |
29
- | [cli.md](./docs/cli.md) | Install and use the global `josh` CLI |
30
- | [package.md](./docs/package.md) | Use the kit as a project devDependency — configs, prompts, scripts, `josh init` |
31
- | [publishing.md](./docs/publishing.md) | First public npm release and trusted publishing setup |
32
- | [josh-commands.md](./docs/josh-commands.md) | Full `josh` CLI command reference |
33
- | [overview.md](./docs/overview.md) | What the kit provides and how it works |
34
- | [troubleshooting.md](./docs/troubleshooting.md) | `401`/`404` auth errors, `josh: command not found`, stale shim, version mismatch |
33
+ **Get started**
34
+
35
+ - [getting-started.md](./docs/getting-started.md) — first install for an `index.html` site or a non-Node project (`static`)
36
+ - [package.md](./docs/package.md) — add kit to a Node project (`node`)
37
+ - [how-to.md](./docs/how-to.md) — step-by-step guides by task: update, release, run Issues, fix a failing gate
38
+
39
+ **Understand kit**
40
+
41
+ - [why.md](./docs/why.md) — the problems kit exists to solve
42
+ - [overview.md](./docs/overview.md) — what kit sets up and how it works
43
+
44
+ **Commands and configuration**
45
+
46
+ - [cli.md](./docs/cli.md) — install the global `josh` command
47
+ - [josh-commands.md](./docs/josh-commands.md) — every `josh` command ([catalog](./docs/josh-command-catalog.md))
48
+ - [init.md](./docs/init.md) — what `josh init` creates, per profile
49
+ - [sync.md](./docs/sync.md) — what `josh sync` updates after an upgrade
50
+ - [manual-config.md](./docs/manual-config.md) — use the presets without `josh init`
51
+ - [package-api.md](./docs/package-api.md) — the config presets and libraries the package exports
52
+
53
+ **AI workflow and operations**
54
+
55
+ - [scripts-ai.md](./docs/scripts-ai.md) — Telegram notifications and the Issue workflow commands
56
+ - [cloud-session.md](./docs/cloud-session.md) — running in an agent container
57
+ - [eval.md](./docs/eval.md) — measuring whether the agent rules are followed
58
+ - [troubleshooting.md](./docs/troubleshooting.md) — install and auth errors
59
+ - [authentication.md](./docs/authentication.md) — existing GitHub Packages installs only
35
60
 
36
61
  ## Contributing
37
62
 
38
- Community standards live in [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md); security reports go through [SECURITY.md](./SECURITY.md). Development conventions are documented in `CLAUDE.md`; `AGENTS.md` and `GEMINI.md` are pointers to it.
63
+ Community standards live in [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md); security reports go through [SECURITY.md](./SECURITY.md). Development conventions are in [CLAUDE.md](./CLAUDE.md); releases are in [publishing.md](./docs/publishing.md), and maintainer records in [docs/maintainers/](./docs/maintainers/).
39
64
 
40
65
  ## License
41
66
 
42
- MIT
67
+ [MIT](./LICENSE)
@@ -9,10 +9,10 @@ import {
9
9
  load_environment_file,
10
10
  notice_refusal_path,
11
11
  refusal_path
12
- } from "./chunk-ROULPSQC.js";
13
- import "./chunk-7XQ6TK3R.js";
14
- import "./chunk-SXPTRFPG.js";
15
- import "./chunk-2PGRQ62K.js";
12
+ } from "./chunk-V7RH56HU.js";
13
+ import "./chunk-OM5VDM6F.js";
14
+ import "./chunk-O3PEDF3M.js";
15
+ import "./chunk-EYFJ74MJ.js";
16
16
  import "./chunk-WPALEIRX.js";
17
17
  import "./chunk-RKUAD3I6.js";
18
18
  export {
@@ -1,17 +1,18 @@
1
1
  import {
2
2
  lane_child_marker,
3
3
  run_issue_number
4
- } from "./chunk-SXPTRFPG.js";
4
+ } from "./chunk-O3PEDF3M.js";
5
5
  import {
6
6
  PLATFORM_TEMP_ROOT,
7
7
  cost_blocks,
8
8
  cost_usage,
9
+ json_value,
9
10
  lane_paths,
10
11
  stamp_file,
11
12
  time_density_hook,
12
13
  time_shell,
13
14
  time_transcript_line
14
- } from "./chunk-2PGRQ62K.js";
15
+ } from "./chunk-EYFJ74MJ.js";
15
16
  import {
16
17
  ENV_FILE_NAME,
17
18
  LANE_SEAT_KEY,
@@ -111,6 +112,7 @@ var filing_cap = {
111
112
  import { platform } from "node:os";
112
113
  var GIT_COMMAND_UNIX = "/usr/bin/git";
113
114
  var PORCELAIN_FLAG = "--porcelain";
115
+ var UNTRACKED_FILES_FLAG = "--untracked-files=all";
114
116
  function get_git_command() {
115
117
  if (platform() === "win32") {
116
118
  return String.raw`"C:\Program Files\Git\cmd\git.exe"`;
@@ -242,7 +244,7 @@ async function branch() {
242
244
  return await git_spawn.read(["rev-parse", "--abbrev-ref", "HEAD"]);
243
245
  }
244
246
  async function status() {
245
- return await git_spawn.read(["status", PORCELAIN_FLAG, "--untracked-files=normal"]);
247
+ return await git_spawn.read(["status", PORCELAIN_FLAG, UNTRACKED_FILES_FLAG]);
246
248
  }
247
249
  async function repository_root() {
248
250
  return await git_spawn.read(["rev-parse", "--show-toplevel"]);
@@ -1516,6 +1518,112 @@ var run_cut = {
1516
1518
  worktree_git_directory_sync
1517
1519
  };
1518
1520
 
1521
+ // scripts/rules/shell-segments.ts
1522
+ var SEGMENT_SEPARATOR = /&&|\|\||;|\n/u;
1523
+ function segments_of(command) {
1524
+ return command.split(SEGMENT_SEPARATOR).map((segment) => segment.trim());
1525
+ }
1526
+ function is_josh_command(segment, names) {
1527
+ const named = time_shell.josh_command_of(segment);
1528
+ if (!named.startsWith(time_shell.JOSH_PREFIX)) return false;
1529
+ return names.has(named.slice(time_shell.JOSH_PREFIX.length));
1530
+ }
1531
+ var shell_segments = {
1532
+ SEGMENT_SEPARATOR,
1533
+ is_josh_command,
1534
+ segments_of
1535
+ };
1536
+
1537
+ // scripts/rules/run-tail.ts
1538
+ var SEGMENT_PREFIX = String.raw`(?:\(\s*)?(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)*(?:npx\s+|pnpm\s+(?:exec\s+|run\s+)?)?`;
1539
+ var WORD_END = String.raw`(?=\s|$|\))`;
1540
+ var PUSH_STEP_SEGMENT = new RegExp(String.raw`^${SEGMENT_PREFIX}josh\s+(?:git|g)${WORD_END}`, "u");
1541
+ var CONFIRMED_FLAG = new RegExp(String.raw`(?:^|\s)(?:-y|--yes)${WORD_END}`, "u");
1542
+ var SKIPS_THE_PUSH = new RegExp(String.raw`(?:^|\s)--skip-push${WORD_END}`, "u");
1543
+ var BACKGROUND_KEY = "run_in_background";
1544
+ var QUOTED_SPAN = /"[^"]*"|'[^']*'/gu;
1545
+ function flags_of(segment) {
1546
+ return segment.replaceAll(QUOTED_SPAN, " ");
1547
+ }
1548
+ function is_push_step_segment(segment) {
1549
+ if (!PUSH_STEP_SEGMENT.test(segment)) return false;
1550
+ const flags = flags_of(segment);
1551
+ if (SKIPS_THE_PUSH.test(flags)) return false;
1552
+ return CONFIRMED_FLAG.test(flags);
1553
+ }
1554
+ function is_push_step(command) {
1555
+ return shell_segments.segments_of(command).some((segment) => is_push_step_segment(segment));
1556
+ }
1557
+ function is_backgrounded(input) {
1558
+ if (!json_value.is_record(input)) return false;
1559
+ return input[BACKGROUND_KEY] === true;
1560
+ }
1561
+ function is_push_step_call(call) {
1562
+ if (call.name !== cost_blocks.BASH_TOOL) return false;
1563
+ return is_push_step(time_shell.bash_command(call.input));
1564
+ }
1565
+ function is_backgrounded_push_step(call) {
1566
+ if (!is_backgrounded(call.input)) return false;
1567
+ return is_push_step_call(call);
1568
+ }
1569
+ function is_foreground_push_step(call) {
1570
+ if (is_backgrounded(call.input)) return false;
1571
+ return is_push_step_call(call);
1572
+ }
1573
+ function decide3() {
1574
+ return true;
1575
+ }
1576
+ var RUN_TAIL_REASON = '\u26D4 run tail: this issues the commit-push-PR step in the foreground, where the harness decides when to stop waiting. Reissue it with `run_in_background` set. In `fullrun #1501` this call was given a 900-second timeout, the harness detached it at its own 600-second cap, and nothing read the output for a further 6m28s \u2014 45m03s of wall clock on about 17 minutes of work (joshuafolkken/kit#1510). **Never give a foreground call a timeout above the harness cap**: the cap decides, not the number. A backgrounded call re-invokes you when it exits, so the completion notification is what resumes the run. **When it arrives, the turn that reads it goes straight through any branch-2 filing and `pnpm josh epic:bundle` to `pnpm josh followup` \u2014 the turn never ends at the push.** CI went green 6m06s before the merge started in that run, and it started only because the person asked; joshuafolkken/kit#1333 had already settled that the merge is issued in the turn the review closes. While the push runs, do the work that writes nothing \u2014 drafting the completion body to a file, deciding the branch-2 disposition. `pnpm josh followup` itself stays in the **foreground** \u2014 nearly every step after it reads its result. But a tail does follow the merge, measured at 3.0 min and 5.9% of a run (joshuafolkken/kit#1462), so empty it beforehand: the steps that read the merge result stay after `followup`, and the rest are composed in the turn that issues it, bar the one exception background-commands.md names. The procedure is `.claude/skills/workflow-commands/background-commands.md`, "A command that can take minutes is issued in the background". **This rule fires on every foreground push, not once per run**, so reissuing the same call in the foreground will be refused again.';
1577
+ var run_tail = {
1578
+ RUN_TAIL_REASON,
1579
+ decide: decide3,
1580
+ is_backgrounded_push_step,
1581
+ is_foreground_push_step,
1582
+ is_push_step,
1583
+ is_push_step_call
1584
+ };
1585
+
1586
+ // scripts/rules/lane-background.ts
1587
+ var LONG_RUNNING_COMMANDS = new Set(
1588
+ ["gate", "git", "followup"].map((name) => `${time_shell.JOSH_PREFIX}${name}`)
1589
+ );
1590
+ var BACKGROUND_KEY2 = "run_in_background";
1591
+ function is_backgrounded2(input) {
1592
+ return json_value.is_record(input) && input[BACKGROUND_KEY2] === true;
1593
+ }
1594
+ function runs_long_command(command) {
1595
+ return time_shell.josh_commands_of(command).some((name) => LONG_RUNNING_COMMANDS.has(name));
1596
+ }
1597
+ function is_candidate(call) {
1598
+ if (run_tail.is_push_step_call(call)) return true;
1599
+ if (call.name !== cost_blocks.BASH_TOOL || !is_backgrounded2(call.input)) return false;
1600
+ return runs_long_command(time_shell.bash_command(call.input));
1601
+ }
1602
+ function is_background_long_run(call, is_lane_child = () => lane_child_marker.is_child_of(process.cwd())) {
1603
+ return is_candidate(call) && is_lane_child();
1604
+ }
1605
+ function pending_background_ids(tail) {
1606
+ const lines = tail.split("\n").map((line) => time_transcript_line.parse_line(line));
1607
+ const finished = new Set(lines.map((line) => line?.finished_background ?? ""));
1608
+ const launched = lines.flatMap((line) => line?.blocks.map((block) => block.background_id) ?? []);
1609
+ return launched.filter((id) => id !== "" && !finished.has(id));
1610
+ }
1611
+ var LANE_BACKGROUND_REASON = '\u26D4 lane background: this session is a dispatched lane child (`JOSH_LANE_CHILD`), a headless `claude -p` process whose background Bash tasks are killed when the turn ends \u2014 so a backgrounded `josh gate` / `josh git` / `josh followup` never re-invokes you; it dies at `exit code 143` (joshuafolkken/kit#2704, measured on the #2606 child: implementation, review and live verification done, the run parked instead of merged). Hand the gate-to-merge region to the detached supervisor instead: `pnpm josh ship --detach --review "<title> #<N>"` (add `--cite <N>`), then end the turn on `launched` / `busy` \u2014 `.claude/skills/workflow-commands/chain-rule.md` step 0 is the single source. A single check you must read before continuing runs in the foreground within the tool timeout; the commit-push step does not \u2014 in a child it too belongs to the detached ship, in either spelling. This rule fires on every occurrence, not once per run.';
1612
+ var LANE_BACKGROUND_STOP_REASON = "\u26D4 lane background stop: this is a dispatched lane child (`JOSH_LANE_CHILD`) and a background task it launched has not finished \u2014 ending this headless turn kills it, and nothing re-invokes you afterwards (joshuafolkken/kit#2704). Do not send a notification and do not stop: wait for the task in the foreground and act on its result, or re-issue the work through `pnpm josh ship --detach`, whose supervisor outlives the turn (`.claude/skills/workflow-commands/chain-rule.md` step 0). Do not repeat your previous reply.";
1613
+ var ROW2 = {
1614
+ id: "lane-background",
1615
+ is_trigger: (call) => is_background_long_run(call),
1616
+ reason: LANE_BACKGROUND_REASON,
1617
+ decide: () => true
1618
+ };
1619
+ var lane_background = {
1620
+ LANE_BACKGROUND_REASON,
1621
+ LANE_BACKGROUND_STOP_REASON,
1622
+ ROW: ROW2,
1623
+ is_background_long_run,
1624
+ pending_background_ids
1625
+ };
1626
+
1519
1627
  // scripts/git/issue-labels.ts
1520
1628
  var BUG_LABEL = "bug";
1521
1629
  var ENHANCEMENT_LABEL = "enhancement";
@@ -1535,22 +1643,6 @@ function has_label_name(labels, wanted) {
1535
1643
  return label_name_of(labels, wanted) !== void 0;
1536
1644
  }
1537
1645
 
1538
- // scripts/rules/shell-segments.ts
1539
- var SEGMENT_SEPARATOR = /&&|\|\||;|\n/u;
1540
- function segments_of(command) {
1541
- return command.split(SEGMENT_SEPARATOR).map((segment) => segment.trim());
1542
- }
1543
- function is_josh_command(segment, names) {
1544
- const named = time_shell.josh_command_of(segment);
1545
- if (!named.startsWith(time_shell.JOSH_PREFIX)) return false;
1546
- return names.has(named.slice(time_shell.JOSH_PREFIX.length));
1547
- }
1548
- var shell_segments = {
1549
- SEGMENT_SEPARATOR,
1550
- is_josh_command,
1551
- segments_of
1552
- };
1553
-
1554
1646
  // scripts/rules/lane-park.ts
1555
1647
  var NOTIFY_COMMANDS = /* @__PURE__ */ new Set(["notify"]);
1556
1648
  var CONFIRMATION_TASK_TYPE = /(?:^|\s)--task-type[=\s]+["']?confirmation\b/u;
@@ -3300,6 +3392,8 @@ export {
3300
3392
  ALREADY_DONE_LABEL,
3301
3393
  has_any_label,
3302
3394
  has_label_name,
3395
+ run_tail,
3396
+ lane_background,
3303
3397
  lane_park,
3304
3398
  repo_party,
3305
3399
  FULL_PAGE_QUERY,
@@ -18,7 +18,6 @@ var json_value = { parse_or_undefined, is_record };
18
18
  // scripts/josh/josh-command-types.ts
19
19
  var OPTIONAL_ENV_FILE_FLAGS = [`--env-file-if-exists=${ENV_FILE_NAME}`];
20
20
  var GATE_COMMAND = "gate";
21
- var PE = ["pnpm", "exec"];
22
21
  var ESLINT_CACHE_FILE = ".eslintcache";
23
22
  var TS_BUILD_INFO_FILE = ".tsbuildinfo";
24
23
  var CSPELL_CACHE_FILE = ".cspellcache";
@@ -56,7 +55,6 @@ function content_cache_flags(cache_file) {
56
55
  var ESLINT_CACHE_FLAGS = content_cache_flags(ESLINT_CACHE_FILE);
57
56
  var ESLINT_EDIT_CACHE_FLAGS = content_cache_flags(ESLINT_EDIT_CACHE_FILE);
58
57
  var ESLINT_RELATED_CACHE_FLAGS = content_cache_flags(ESLINT_RELATED_CACHE_FILE);
59
- var TS_CACHE_FLAGS = ["--incremental", "--tsBuildInfoFile", TS_BUILD_INFO_FILE];
60
58
  var CSPELL_CACHE_FLAGS = content_cache_flags(CSPELL_CACHE_FILE);
61
59
 
62
60
  // scripts/cost-runtime/cost-blocks.ts
@@ -502,6 +500,12 @@ var AI_COMMANDS = {
502
500
  reference: ["[issue|--force]", "automation", ["files"]],
503
501
  default_script_arguments: ["--release"]
504
502
  },
503
+ "run:tidy": {
504
+ script: "scripts/run/run-tidy-cli.ts",
505
+ description: "Close merged lanes and drop stashes whose issues are all merged",
506
+ category: "AI tools",
507
+ reference: ["", "automation", ["git", "network", "files"]]
508
+ },
505
509
  "run:carry": {
506
510
  script: "scripts/run/run-carry-cli.ts",
507
511
  description: "Carry one invocation\u2019s budget across its own session cuts",
@@ -698,7 +702,7 @@ var DEV_COMMANDS = {
698
702
  },
699
703
  lint: {
700
704
  script: "scripts/lint/lint-parallel.ts",
701
- description: "Check code with prettier and eslint",
705
+ description: "Check code with prettier and eslint (skips a tool a static project lacks)",
702
706
  category: "Development",
703
707
  reference: ["", "developer", ["processes"]]
704
708
  },
@@ -734,7 +738,7 @@ var DEV_COMMANDS = {
734
738
  },
735
739
  format: {
736
740
  script: "scripts/lint/format.ts",
737
- description: "Format code with prettier and eslint",
741
+ description: "Format code with prettier and eslint (skips a tool a static project lacks)",
738
742
  category: "Development",
739
743
  reference: ["", "developer", ["files", "processes"]]
740
744
  },
@@ -846,8 +850,8 @@ var DEV_COMMANDS = {
846
850
  argument_targets: ["test:unit", "test:e2e"]
847
851
  },
848
852
  check: {
849
- shell: [...PE, "tsc", "--noEmit", ...TS_CACHE_FLAGS],
850
- description: "Type-check TypeScript project",
853
+ script: "scripts/gate/type-check-command.ts",
854
+ description: "Type-check with tsc (skips a static project with no TypeScript to check)",
851
855
  category: "Development",
852
856
  reference: ["[arguments...]", "developer", ["processes"]]
853
857
  },
@@ -1336,6 +1340,7 @@ var ALIASES = {
1336
1340
  rh: "run:hold",
1337
1341
  rr: "run:release",
1338
1342
  rc: "run:carry",
1343
+ rtd: "run:tidy",
1339
1344
  rw: "run:wake",
1340
1345
  rct: "run:cut",
1341
1346
  rv: "run:liveness",