@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.
- package/.claude/skills/workflow-commands/background-commands.md +6 -0
- package/.claude/skills/workflow-commands/backlogrun-child.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +4 -3
- package/.claude/skills/workflow-commands/observation-filing.md +3 -3
- package/.claude/skills/workflow-commands/prerequisite.md +1 -1
- package/.github/workflows/ci.yml +26 -8
- package/.github/workflows/pr-classification.yml +76 -0
- package/README.md +47 -22
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-GQ3EVWHY.js → chunk-3FWV6CFC.js} +113 -19
- package/dist/hooks/{chunk-2PGRQ62K.js → chunk-EYFJ74MJ.js} +11 -6
- package/dist/hooks/{chunk-YO43HEPY.js → chunk-GIX5SAAB.js} +237 -235
- package/dist/hooks/{chunk-LCJZE42I.js → chunk-IA7WC4NS.js} +1 -1
- package/dist/hooks/{chunk-BNFG2IFK.js → chunk-IBFC25IX.js} +4 -4
- package/dist/hooks/{chunk-I3JP3D4A.js → chunk-M4U4YNRE.js} +3 -3
- package/dist/hooks/{chunk-NJZSO7BK.js → chunk-NKVLBB65.js} +3 -3
- package/dist/hooks/{chunk-SXPTRFPG.js → chunk-O3PEDF3M.js} +1 -1
- package/dist/hooks/{chunk-7XQ6TK3R.js → chunk-OM5VDM6F.js} +2 -2
- package/dist/hooks/{chunk-ROULPSQC.js → chunk-V7RH56HU.js} +2 -2
- package/dist/hooks/codex-hook-adapter.js +10 -10
- package/dist/hooks/duplicate-read-guard.js +5 -5
- package/dist/hooks/format-edited.js +2 -2
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +9 -9
- package/dist/hooks/stop-guard.js +9 -3
- package/dist/josh.js +11 -6
- package/package.json +13 -1
- package/prompts/collaboration-workflow/issue-template.md +1 -1
- package/scripts/ci/pr-classification.ts +27 -6
- package/scripts/document/document-byte-budget.ts +1 -1
- package/scripts/gate/hook-gate-reuse.ts +1 -1
- package/scripts/gate/project-checks.ts +45 -15
- package/scripts/gate/type-check-command.ts +43 -0
- package/scripts/git/constants.ts +4 -0
- package/scripts/git/git-command.ts +11 -5
- package/scripts/git/git-gh-pr-rollup.ts +56 -16
- package/scripts/git/git-staging.ts +6 -4
- package/scripts/git/git-stash.ts +73 -1
- package/scripts/git/stash-orphans.ts +24 -2
- package/scripts/git/stash-sweep-lock.ts +141 -0
- package/scripts/hooks/stop-guard.ts +2 -0
- package/scripts/init/init-actions.ts +3 -2
- package/scripts/init/init-logic-migrate.ts +10 -1
- package/scripts/init/init-logic-templates.ts +27 -7
- package/scripts/init/init-logic-workspace.ts +52 -1
- package/scripts/init/init-logic.ts +6 -1
- package/scripts/init/init.ts +60 -19
- package/scripts/issue/issue-merged.ts +75 -0
- package/scripts/issue/measure-rerun-cli.ts +2 -2
- package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +10 -0
- package/scripts/josh/josh-command-map.ts +1 -0
- package/scripts/josh/josh-command-types.ts +0 -3
- package/scripts/josh/josh-commands-ai.ts +6 -0
- package/scripts/josh/josh-commands-development.ts +5 -5
- package/scripts/lint/lint-parallel.ts +19 -18
- package/scripts/observations/observation-ledger-home.ts +22 -2
- package/scripts/observations/observation-ledger-line.ts +11 -0
- package/scripts/observations/observation-ledger-migrate.ts +146 -0
- package/scripts/observations/observation-ledger.ts +51 -3
- package/scripts/observations/observations-flush.ts +38 -7
- package/scripts/review/review-finding-ledger.ts +1 -1
- package/scripts/review/review-record-cli.ts +1 -7
- package/scripts/rules/delivered-rules.ts +10 -0
- package/scripts/rules/lane-background.ts +111 -0
- package/scripts/rules/stop-rules.ts +15 -2
- package/scripts/run/run-carry-flush.ts +2 -7
- package/scripts/run/run-hold-cli.ts +10 -2
- package/scripts/run/run-tidy-cli.ts +50 -0
- package/scripts/run/run-tidy-lanes.ts +110 -0
- package/scripts/run/run-tidy-stashes.ts +160 -0
- package/scripts/run/run-tidy.ts +101 -0
- package/scripts/safe-chain/ci-installer-pin.ts +90 -0
- package/scripts/safe-chain/preinstall-command.ts +39 -0
- package/scripts/sync/sync.ts +5 -2
- package/scripts/test/pilot-files.ts +0 -1
- package/scripts/version/preinstall-version-update.ts +2 -0
- package/templates/workflows/ci.yml +21 -7
- 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
|
|
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.
|
|
115
|
-
|
|
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
|
|
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
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
6
|
-
- **Project config package** — ESLint / Prettier / tsconfig presets, prompts, and scripts consumed as a devDependency.
|
|
5
|
+
## What your project gets
|
|
7
6
|
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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 -
|
|
17
|
-
josh
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
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-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
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,
|
|
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
|
-
|
|
850
|
-
description: "Type-check TypeScript
|
|
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",
|