@joshuafolkken/kit 1.887.0 → 1.929.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/backlogrun-progress.md +13 -12
- package/.claude/skills/workflow-commands/backlogrun-steps.md +2 -1
- package/.claude/skills/workflow-commands/fullrun-steps.md +4 -2
- package/.claude/skills/workflow-commands/halfrun.md +3 -1
- package/.claude/skills/workflow-commands/kickoff.md +5 -3
- package/.claude/skills/workflow-commands/observation-filing.md +3 -1
- package/.claude/skills/workflow-commands/prerequisite.md +3 -1
- package/.claude/skills/workflow-commands/target-repository.md +1 -1
- package/.github/pull_request_template.md +12 -0
- package/.github/release.yml +3 -0
- package/.github/workflows/ci.yml +8 -0
- package/.github/workflows/production.yml +10 -1
- package/.github/workflows/publish.yml +72 -2
- package/CLAUDE.md +2 -0
- package/README.md +2 -1
- package/dist/CLAUDE.md +2 -0
- package/dist/CLAUDE.static.md +8 -0
- package/dist/hooks/batch-guard.js +6 -6
- package/dist/hooks/{chunk-264QY6KS.js → chunk-2PGRQ62K.js} +25 -10
- package/dist/hooks/chunk-3V37IID6.js +82 -0
- package/dist/hooks/{chunk-36UE53LS.js → chunk-7XQ6TK3R.js} +2 -2
- package/dist/hooks/{chunk-PRDCNGA6.js → chunk-BNFG2IFK.js} +4 -4
- package/dist/hooks/{chunk-VGYSA2NT.js → chunk-GQ3EVWHY.js} +22 -3
- package/dist/hooks/{chunk-Y5OYCU2F.js → chunk-I3JP3D4A.js} +3 -3
- package/dist/hooks/{chunk-6MQCXEEA.js → chunk-LCJZE42I.js} +25 -88
- package/dist/hooks/{chunk-ZBK4XRAS.js → chunk-NJZSO7BK.js} +3 -3
- package/dist/hooks/{chunk-SSX23SJ2.js → chunk-RKUAD3I6.js} +1 -0
- package/dist/hooks/{chunk-FRJKVSK3.js → chunk-ROULPSQC.js} +2 -2
- package/dist/hooks/{chunk-FNFOLFFV.js → chunk-SXPTRFPG.js} +2 -2
- package/dist/hooks/{chunk-FXAKJKTB.js → chunk-WPALEIRX.js} +1 -1
- package/dist/hooks/{chunk-4OXFU3MK.js → chunk-YO43HEPY.js} +634 -156
- package/dist/hooks/codex-hook-adapter.js +13 -12
- package/dist/hooks/duplicate-read-guard.js +7 -7
- package/dist/hooks/format-edited.js +4 -3
- package/dist/hooks/investigation-guard.js +7 -7
- package/dist/hooks/pretool-guard.js +12 -11
- package/dist/hooks/session-lang.js +2 -2
- package/dist/hooks/stop-guard.js +5 -5
- package/dist/josh.js +156 -20
- package/eslint/base.js +4 -1
- package/package.json +72 -18
- package/playwright.config.ts +9 -17
- package/ports/index.d.ts +1 -0
- package/ports/index.js +1 -0
- package/ports/web-server.d.ts +5 -0
- package/ports/web-server.js +73 -0
- package/prettier/index.js +3 -8
- package/prettier/static.d.ts +3 -0
- package/prettier/static.js +10 -0
- package/prompts/collaboration-workflow/issue-template.md +11 -4
- package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
- package/prompts/collaboration-workflow/wip-cap.md +2 -2
- package/prompts/review.md +2 -2
- package/prompts/testing-guide.md +1 -0
- package/scripts/agent/agent-role-profile.ts +11 -0
- package/scripts/build/build-claude-md.ts +6 -1
- package/scripts/ci/pr-classification.ts +114 -0
- package/scripts/document/document-byte-budget.ts +3 -2
- package/scripts/gate/gate-report.ts +2 -0
- package/scripts/gate/gate-steps.ts +50 -0
- package/scripts/gate/project-checks.ts +145 -0
- package/scripts/gate/verification-gate.ts +14 -43
- package/scripts/git/git-common-directory.ts +9 -1
- package/scripts/git/git-gh-pr-rollup.ts +29 -1
- package/scripts/git/git-gh-pr.ts +61 -1
- package/scripts/git/git-pr.ts +62 -13
- package/scripts/git/issue-labels.ts +9 -0
- package/scripts/init/distributed-paths.ts +9 -5
- package/scripts/init/hook-command-bootstrap.ts +40 -8
- package/scripts/init/hook-command-rewrite.ts +7 -3
- package/scripts/init/init-actions.ts +94 -7
- package/scripts/init/init-ai-copy.ts +107 -19
- package/scripts/init/init-logic-workspace.ts +38 -5
- package/scripts/init/init-logic.ts +1 -0
- package/scripts/init/init-static.ts +55 -0
- package/scripts/init/init.ts +97 -30
- package/scripts/init/project-profile-cli.ts +14 -0
- package/scripts/init/project-profile.ts +158 -0
- package/scripts/init/start.ts +30 -0
- package/scripts/issue/issue-bug-label.ts +13 -0
- package/scripts/issue/issue-classification.ts +74 -0
- package/scripts/issue/issue-lint-cli.ts +10 -3
- package/scripts/issue/markdown-section.ts +38 -4
- package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +27 -1
- package/scripts/josh/josh-command-map.ts +3 -0
- package/scripts/josh/josh-commands-development.ts +8 -15
- package/scripts/josh/josh-commands-project.ts +14 -1
- package/scripts/josh/josh-logic.ts +13 -0
- package/scripts/josh/run-github-prerequisite.ts +32 -0
- package/scripts/lane/lane-await.ts +8 -1
- package/scripts/lane/openai-lane-ship.ts +30 -0
- package/scripts/lane/openai-lane-supervisor.ts +35 -8
- package/scripts/lane/openai-review-broker.ts +260 -0
- package/scripts/lint/cspell-cached.ts +21 -0
- package/scripts/lint/format.ts +68 -0
- package/scripts/lint/lint-parallel.ts +30 -1
- package/scripts/observations/observations-flush.ts +1 -1
- package/scripts/package/production-dependency-graph.ts +68 -0
- package/scripts/package/verify-optional-eslint-install.ts +62 -0
- package/scripts/release/github-release-cli.ts +11 -0
- package/scripts/release/github-release.ts +220 -0
- package/scripts/release/publish-tag-cli.ts +8 -0
- package/scripts/release/publish-tag.ts +11 -0
- package/scripts/release/release-publish.ts +7 -2
- package/scripts/rules/delivered-rules.ts +2 -0
- package/scripts/rules/issue-bug-label.ts +85 -0
- package/scripts/rules/issue-filing-args.ts +50 -0
- package/scripts/run/run-merge-cli.ts +19 -0
- package/scripts/run/run-merge.ts +9 -1
- package/scripts/run/run-ship-cli.ts +20 -0
- package/scripts/run/run-ship-detach.ts +180 -14
- package/scripts/run/run-ship-review-steps.ts +45 -25
- package/scripts/run/run-step-cli.ts +11 -1
- package/scripts/run/run-step.ts +36 -8
- package/scripts/run/run-wake-session.ts +3 -1
- package/scripts/test/pilot-files.ts +0 -2
- package/scripts/test/skip-marker.ts +3 -0
- package/scripts/test/test-declared-changed.ts +2 -1
- package/scripts/test/test-declared-logic.ts +15 -10
- package/scripts/test/test-declared.ts +20 -8
- package/scripts/test/test-red-logic.ts +3 -3
- package/scripts/test/test-unit-guard.ts +24 -12
- package/templates/CLAUDE.static.md +8 -0
- package/templates/cursorrules.static +1 -0
- package/templates/pnpm-workspace.static.yaml +4 -0
- package/templates/prettierignore.static +12 -0
- package/templates/workflows/ci.yml +5 -0
|
@@ -27,6 +27,7 @@ const TEST_SUFFIXES: ReadonlyArray<string> = ['.e2e.ts', '.test.ts']
|
|
|
27
27
|
const EXEMPT_PATHS: ReadonlyArray<string> = ['.editorconfig']
|
|
28
28
|
const EXEMPT_PREFIXES: ReadonlyArray<string> = ['.idea/', '.vscode/', 'prompts/']
|
|
29
29
|
const EXEMPT_SUFFIXES: ReadonlyArray<string> = ['.md']
|
|
30
|
+
const STATIC_VISUAL_SUFFIXES: ReadonlyArray<string> = ['.html', '.css']
|
|
30
31
|
|
|
31
32
|
function normalize(paths: ReadonlyArray<string>): Array<string> {
|
|
32
33
|
return paths.map((path) => path.trim()).filter((path) => path !== '')
|
|
@@ -36,8 +37,12 @@ function is_test_file(path: string): boolean {
|
|
|
36
37
|
return TEST_SUFFIXES.some((suffix) => path.endsWith(suffix))
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
function
|
|
40
|
-
|
|
40
|
+
function is_static_visual(path: string, is_static: boolean): boolean {
|
|
41
|
+
return is_static && STATIC_VISUAL_SUFFIXES.some((suffix) => path.endsWith(suffix))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function is_exempt(path: string, is_static = false): boolean {
|
|
45
|
+
if (EXEMPT_PATHS.includes(path) || is_static_visual(path, is_static)) return true
|
|
41
46
|
|
|
42
47
|
return (
|
|
43
48
|
EXEMPT_SUFFIXES.some((suffix) => path.endsWith(suffix)) ||
|
|
@@ -47,31 +52,31 @@ function is_exempt(path: string): boolean {
|
|
|
47
52
|
|
|
48
53
|
// A runtime file is one that is neither a test nor exempt. Its presence with no test file beside it is
|
|
49
54
|
// the whole of what `required` reports.
|
|
50
|
-
function is_runtime(path: string): boolean {
|
|
51
|
-
return !is_test_file(path) && !is_exempt(path)
|
|
55
|
+
function is_runtime(path: string, is_static = false): boolean {
|
|
56
|
+
return !is_test_file(path) && !is_exempt(path, is_static)
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
// **A test file changed → `satisfied`, whatever else did.** A runtime file with no test beside it →
|
|
55
60
|
// `required`. Everything left — an all-exempt change, and the empty diff — is `exempt`, because there
|
|
56
61
|
// is no runtime file the change failed to test.
|
|
57
|
-
function verdict_for(paths: ReadonlyArray<string
|
|
62
|
+
function verdict_for(paths: ReadonlyArray<string>, is_static = false): Verdict {
|
|
58
63
|
const changed = normalize(paths)
|
|
59
64
|
|
|
60
65
|
if (changed.some((path) => is_test_file(path))) return 'satisfied'
|
|
61
|
-
if (changed.some((path) => is_runtime(path))) return 'required'
|
|
66
|
+
if (changed.some((path) => is_runtime(path, is_static))) return 'required'
|
|
62
67
|
|
|
63
68
|
return 'exempt'
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
// The runtime files with no test beside them — the `required` detail line, so the answer says which
|
|
67
72
|
// files it is about rather than only that it refused.
|
|
68
|
-
function runtime_files(paths: ReadonlyArray<string
|
|
69
|
-
return normalize(paths).filter((path) => is_runtime(path))
|
|
73
|
+
function runtime_files(paths: ReadonlyArray<string>, is_static = false): Array<string> {
|
|
74
|
+
return normalize(paths).filter((path) => is_runtime(path, is_static))
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
// The paths that made the change exempt — the `exempt` detail line.
|
|
73
|
-
function exempt_files(paths: ReadonlyArray<string
|
|
74
|
-
return normalize(paths).filter((path) => is_exempt(path))
|
|
78
|
+
function exempt_files(paths: ReadonlyArray<string>, is_static = false): Array<string> {
|
|
79
|
+
return normalize(paths).filter((path) => is_exempt(path, is_static))
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
const test_declared_logic = {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { text } from 'node:stream/consumers'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
import { parseArgs } from 'node:util'
|
|
5
|
+
import { project_checks } from '#scripts/gate/project-checks'
|
|
5
6
|
import { test_declared_changed } from './test-declared-changed'
|
|
6
7
|
import { test_declared_logic, type Verdict } from './test-declared-logic'
|
|
7
8
|
import { test_declared_match, type MatchResult } from './test-declared-match'
|
|
@@ -76,24 +77,35 @@ function typed_runtime_file(path: string): string {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
// The word is stdout so a caller can read it alone; the reason — which files, and why — is stderr.
|
|
79
|
-
function
|
|
80
|
+
function exempt_detail(paths: ReadonlyArray<string>, is_static: boolean): string {
|
|
81
|
+
const exempt = test_declared_logic.exempt_files(paths, is_static)
|
|
82
|
+
const has_visual = is_static && exempt.some((path) => /\.(?:html|css)$/u.test(path))
|
|
83
|
+
const instruction = has_visual ? ' — confirm the rendered page in a browser' : ''
|
|
84
|
+
|
|
85
|
+
return `exempt paths: ${exempt.join(', ')}${instruction}`
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function detail_for(verdict: Verdict, paths: ReadonlyArray<string>, is_static = false): string {
|
|
80
89
|
if (verdict === 'required') {
|
|
81
|
-
const typed = test_declared_logic
|
|
90
|
+
const typed = test_declared_logic
|
|
91
|
+
.runtime_files(paths, is_static)
|
|
92
|
+
.map((path) => typed_runtime_file(path))
|
|
82
93
|
|
|
83
94
|
return `runtime files with no test: ${typed.join(', ')}`
|
|
84
95
|
}
|
|
85
96
|
|
|
86
|
-
if (verdict === 'exempt')
|
|
87
|
-
return `exempt paths: ${test_declared_logic.exempt_files(paths).join(', ')}`
|
|
88
|
-
}
|
|
97
|
+
if (verdict === 'exempt') return exempt_detail(paths, is_static)
|
|
89
98
|
|
|
90
99
|
return 'a test file changed'
|
|
91
100
|
}
|
|
92
101
|
|
|
93
|
-
function report(
|
|
94
|
-
|
|
102
|
+
function report(
|
|
103
|
+
paths: ReadonlyArray<string>,
|
|
104
|
+
is_static: boolean = project_checks.is_static(process.cwd()),
|
|
105
|
+
): { detail: string; verdict: Verdict } {
|
|
106
|
+
const verdict = test_declared_logic.verdict_for(paths, is_static)
|
|
95
107
|
|
|
96
|
-
return { detail: detail_for(verdict, paths), verdict }
|
|
108
|
+
return { detail: detail_for(verdict, paths, is_static), verdict }
|
|
97
109
|
}
|
|
98
110
|
|
|
99
111
|
function run(): void {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { issue_bug_label } from '#scripts/issue/issue-bug-label'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import { test_declared_logic } from './test-declared-logic'
|
|
4
4
|
|
|
@@ -16,14 +16,14 @@ type RedVerdict = 'red' | 'green' | 'no-test'
|
|
|
16
16
|
// The declaration a bug-fix Issue carries under its background heading, in the same slot as the
|
|
17
17
|
// behavior-change declaration (`behavior-change-lint.ts`). Read from the declaration, never inferred,
|
|
18
18
|
// so an Issue that does not declare itself a bug is never held to it.
|
|
19
|
-
const BUG_DECLARATION_LINE =
|
|
19
|
+
const { BUG_DECLARATION_LINE } = issue_bug_label
|
|
20
20
|
|
|
21
21
|
// Only a Vitest file can be run against the pre-fix tree here — an `*.e2e.ts` needs a served app, so it
|
|
22
22
|
// is left to the CI E2E job rather than counted as a test this command ran.
|
|
23
23
|
const UNIT_TEST_SUFFIX = '.test.ts'
|
|
24
24
|
|
|
25
25
|
function is_bug_fix(body: string): boolean {
|
|
26
|
-
return
|
|
26
|
+
return issue_bug_label.is_bug_fix(body)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// The changed paths that are Vitest files, in the order they were given.
|
|
@@ -3,7 +3,9 @@ import { existsSync, globSync } from 'node:fs'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
5
|
import { PROJECT_ENVIRONMENT_KEYS } from '#ports'
|
|
6
|
+
import { project_checks } from '#scripts/gate/project-checks'
|
|
6
7
|
import { execa } from 'execa'
|
|
8
|
+
import { SKIP_MARKER } from './skip-marker'
|
|
7
9
|
import { unit_worker_share } from './unit-worker-share'
|
|
8
10
|
|
|
9
11
|
const PNPM = 'pnpm'
|
|
@@ -19,10 +21,10 @@ const NODE_MODULES = 'node_modules'
|
|
|
19
21
|
// - **`vitest` absent is the young project.** `josh init` installs no vitest and writes no
|
|
20
22
|
// `test:unit` script, so a freshly-bootstrapped project always lands here — which is the case the
|
|
21
23
|
// skip was written for, and it keeps it.
|
|
22
|
-
// - **`vitest` present is the project
|
|
23
|
-
// there is a broken state
|
|
24
|
-
//
|
|
25
|
-
//
|
|
24
|
+
// - **`vitest` present is the node project's declaration that it runs unit tests.** Zero matching
|
|
25
|
+
// files there is a broken state — a mis-scoped glob, or a deleted suite — and fails. A static
|
|
26
|
+
// project may carry vitest as an unused dependency without declaring a unit suite; zero files
|
|
27
|
+
// there are reported as skipped, never as a test pass.
|
|
26
28
|
//
|
|
27
29
|
// Rejected: leaving both halves green — joshuafolkken/kit#1216's `--verbose` makes the zero count
|
|
28
30
|
// *readable* in the CI log, but nobody reads a green log and the merge gate reads only pass/fail;
|
|
@@ -35,25 +37,29 @@ const NODE_MODULES = 'node_modules'
|
|
|
35
37
|
// `@playwright/test` is not one: it is an optional peer dependency that vitest's browser mode also
|
|
36
38
|
// needs, so a project can legitimately have it installed with no `*.e2e.{ts,js}` file at all.
|
|
37
39
|
const SKIP_ACTION = 'skip-missing-package'
|
|
40
|
+
const SKIP_NO_TESTS_ACTION = 'skip-no-tests'
|
|
38
41
|
const FAIL_ACTION = 'fail-no-tests'
|
|
39
42
|
|
|
40
|
-
type GuardAction = 'run' | typeof SKIP_ACTION | typeof FAIL_ACTION
|
|
43
|
+
type GuardAction = 'run' | typeof SKIP_ACTION | typeof SKIP_NO_TESTS_ACTION | typeof FAIL_ACTION
|
|
41
44
|
|
|
42
45
|
// The word the gate looks for to know a passing step did not actually run. Exported and reused on
|
|
43
46
|
// both sides rather than matched by eye: joshuafolkken/kit#967 stopped printing a passing check's
|
|
44
47
|
// body, and without this a gate that skipped the whole unit suite printed the same five lines as one
|
|
45
48
|
// that ran it.
|
|
46
|
-
const SKIP_MARKER = '— skipping'
|
|
47
|
-
|
|
48
49
|
const SKIP_REASON = 'vitest is not installed'
|
|
50
|
+
const SKIP_NO_TESTS_REASON = 'no unit test files were found in this static project'
|
|
49
51
|
const FAIL_REASON =
|
|
50
52
|
'vitest is installed but no *.{test,spec}.{ts,js} test file was found — refusing to report a unit check that ran nothing'
|
|
51
53
|
|
|
52
|
-
function resolve_guard_action(
|
|
54
|
+
function resolve_guard_action(
|
|
55
|
+
is_installed: boolean,
|
|
56
|
+
has_tests: boolean,
|
|
57
|
+
is_static = false,
|
|
58
|
+
): GuardAction {
|
|
53
59
|
if (!is_installed) return SKIP_ACTION
|
|
54
|
-
if (
|
|
60
|
+
if (has_tests) return 'run'
|
|
55
61
|
|
|
56
|
-
return
|
|
62
|
+
return is_static ? SKIP_NO_TESTS_ACTION : FAIL_ACTION
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
function is_vitest_installed(project_directory: string): boolean {
|
|
@@ -132,7 +138,9 @@ function report_no_run(action: Exclude<GuardAction, 'run'>, command_label: strin
|
|
|
132
138
|
return FAIL_EXIT_CODE
|
|
133
139
|
}
|
|
134
140
|
|
|
135
|
-
|
|
141
|
+
const reason = action === SKIP_NO_TESTS_ACTION ? SKIP_NO_TESTS_REASON : SKIP_REASON
|
|
142
|
+
|
|
143
|
+
console.info(`josh ${command_label}: ${reason} ${SKIP_MARKER} vitest unit tests.`)
|
|
136
144
|
|
|
137
145
|
return 0
|
|
138
146
|
}
|
|
@@ -149,7 +157,11 @@ async function run_guarded_vitest(
|
|
|
149
157
|
): Promise<number> {
|
|
150
158
|
const is_installed = is_vitest_installed(project_directory)
|
|
151
159
|
const has_tests = has_unit_tests(project_directory)
|
|
152
|
-
const action = resolve_guard_action(
|
|
160
|
+
const action = resolve_guard_action(
|
|
161
|
+
is_installed,
|
|
162
|
+
has_tests,
|
|
163
|
+
project_checks.is_static(project_directory),
|
|
164
|
+
)
|
|
153
165
|
|
|
154
166
|
if (action !== 'run') return report_no_run(action, command_label)
|
|
155
167
|
if (announcement !== undefined) process.stdout.write(`${announcement}\n`)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Project instructions
|
|
2
|
+
|
|
3
|
+
- 既存のファイルと利用者の設定を尊重し、変更する前に内容を確認する。
|
|
4
|
+
- 変更には検証を付け、実行した結果を報告する。
|
|
5
|
+
- HTML、CSS、JavaScript のファイルがある場合は Prettier で整形する。
|
|
6
|
+
- 画面に見える変更をした場合はブラウザーで表示と操作を確認する。
|
|
7
|
+
- JavaScript または TypeScript の動作を変更した場合は、その動作を確認する自動テストを追加する。
|
|
8
|
+
- Git や GitHub を使う場合だけ、それぞれのワークフローを利用する。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Read CLAUDE.md before making changes in this project. It contains the project instructions.
|
|
@@ -6,6 +6,7 @@ on:
|
|
|
6
6
|
|
|
7
7
|
pull_request:
|
|
8
8
|
branches: [main]
|
|
9
|
+
types: [opened, synchronize, reopened, labeled, unlabeled]
|
|
9
10
|
|
|
10
11
|
# A push run is grouped by the commit it carries, so a later push to the default branch never
|
|
11
12
|
# cancels an earlier one. Without the sha every such push shares one group — `github.ref` is
|
|
@@ -220,6 +221,10 @@ jobs:
|
|
|
220
221
|
- name: Run unit tests
|
|
221
222
|
run: pnpm josh test:unit
|
|
222
223
|
|
|
224
|
+
- name: Check release classification
|
|
225
|
+
if: github.event_name == 'pull_request'
|
|
226
|
+
run: pnpm josh pr:classification
|
|
227
|
+
|
|
223
228
|
- name: Check bundle size
|
|
224
229
|
run: pnpm size-limit
|
|
225
230
|
|