@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.
Files changed (127) hide show
  1. package/.claude/skills/workflow-commands/backlogrun-progress.md +13 -12
  2. package/.claude/skills/workflow-commands/backlogrun-steps.md +2 -1
  3. package/.claude/skills/workflow-commands/fullrun-steps.md +4 -2
  4. package/.claude/skills/workflow-commands/halfrun.md +3 -1
  5. package/.claude/skills/workflow-commands/kickoff.md +5 -3
  6. package/.claude/skills/workflow-commands/observation-filing.md +3 -1
  7. package/.claude/skills/workflow-commands/prerequisite.md +3 -1
  8. package/.claude/skills/workflow-commands/target-repository.md +1 -1
  9. package/.github/pull_request_template.md +12 -0
  10. package/.github/release.yml +3 -0
  11. package/.github/workflows/ci.yml +8 -0
  12. package/.github/workflows/production.yml +10 -1
  13. package/.github/workflows/publish.yml +72 -2
  14. package/CLAUDE.md +2 -0
  15. package/README.md +2 -1
  16. package/dist/CLAUDE.md +2 -0
  17. package/dist/CLAUDE.static.md +8 -0
  18. package/dist/hooks/batch-guard.js +6 -6
  19. package/dist/hooks/{chunk-264QY6KS.js → chunk-2PGRQ62K.js} +25 -10
  20. package/dist/hooks/chunk-3V37IID6.js +82 -0
  21. package/dist/hooks/{chunk-36UE53LS.js → chunk-7XQ6TK3R.js} +2 -2
  22. package/dist/hooks/{chunk-PRDCNGA6.js → chunk-BNFG2IFK.js} +4 -4
  23. package/dist/hooks/{chunk-VGYSA2NT.js → chunk-GQ3EVWHY.js} +22 -3
  24. package/dist/hooks/{chunk-Y5OYCU2F.js → chunk-I3JP3D4A.js} +3 -3
  25. package/dist/hooks/{chunk-6MQCXEEA.js → chunk-LCJZE42I.js} +25 -88
  26. package/dist/hooks/{chunk-ZBK4XRAS.js → chunk-NJZSO7BK.js} +3 -3
  27. package/dist/hooks/{chunk-SSX23SJ2.js → chunk-RKUAD3I6.js} +1 -0
  28. package/dist/hooks/{chunk-FRJKVSK3.js → chunk-ROULPSQC.js} +2 -2
  29. package/dist/hooks/{chunk-FNFOLFFV.js → chunk-SXPTRFPG.js} +2 -2
  30. package/dist/hooks/{chunk-FXAKJKTB.js → chunk-WPALEIRX.js} +1 -1
  31. package/dist/hooks/{chunk-4OXFU3MK.js → chunk-YO43HEPY.js} +634 -156
  32. package/dist/hooks/codex-hook-adapter.js +13 -12
  33. package/dist/hooks/duplicate-read-guard.js +7 -7
  34. package/dist/hooks/format-edited.js +4 -3
  35. package/dist/hooks/investigation-guard.js +7 -7
  36. package/dist/hooks/pretool-guard.js +12 -11
  37. package/dist/hooks/session-lang.js +2 -2
  38. package/dist/hooks/stop-guard.js +5 -5
  39. package/dist/josh.js +156 -20
  40. package/eslint/base.js +4 -1
  41. package/package.json +72 -18
  42. package/playwright.config.ts +9 -17
  43. package/ports/index.d.ts +1 -0
  44. package/ports/index.js +1 -0
  45. package/ports/web-server.d.ts +5 -0
  46. package/ports/web-server.js +73 -0
  47. package/prettier/index.js +3 -8
  48. package/prettier/static.d.ts +3 -0
  49. package/prettier/static.js +10 -0
  50. package/prompts/collaboration-workflow/issue-template.md +11 -4
  51. package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
  52. package/prompts/collaboration-workflow/wip-cap.md +2 -2
  53. package/prompts/review.md +2 -2
  54. package/prompts/testing-guide.md +1 -0
  55. package/scripts/agent/agent-role-profile.ts +11 -0
  56. package/scripts/build/build-claude-md.ts +6 -1
  57. package/scripts/ci/pr-classification.ts +114 -0
  58. package/scripts/document/document-byte-budget.ts +3 -2
  59. package/scripts/gate/gate-report.ts +2 -0
  60. package/scripts/gate/gate-steps.ts +50 -0
  61. package/scripts/gate/project-checks.ts +145 -0
  62. package/scripts/gate/verification-gate.ts +14 -43
  63. package/scripts/git/git-common-directory.ts +9 -1
  64. package/scripts/git/git-gh-pr-rollup.ts +29 -1
  65. package/scripts/git/git-gh-pr.ts +61 -1
  66. package/scripts/git/git-pr.ts +62 -13
  67. package/scripts/git/issue-labels.ts +9 -0
  68. package/scripts/init/distributed-paths.ts +9 -5
  69. package/scripts/init/hook-command-bootstrap.ts +40 -8
  70. package/scripts/init/hook-command-rewrite.ts +7 -3
  71. package/scripts/init/init-actions.ts +94 -7
  72. package/scripts/init/init-ai-copy.ts +107 -19
  73. package/scripts/init/init-logic-workspace.ts +38 -5
  74. package/scripts/init/init-logic.ts +1 -0
  75. package/scripts/init/init-static.ts +55 -0
  76. package/scripts/init/init.ts +97 -30
  77. package/scripts/init/project-profile-cli.ts +14 -0
  78. package/scripts/init/project-profile.ts +158 -0
  79. package/scripts/init/start.ts +30 -0
  80. package/scripts/issue/issue-bug-label.ts +13 -0
  81. package/scripts/issue/issue-classification.ts +74 -0
  82. package/scripts/issue/issue-lint-cli.ts +10 -3
  83. package/scripts/issue/markdown-section.ts +38 -4
  84. package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +27 -1
  85. package/scripts/josh/josh-command-map.ts +3 -0
  86. package/scripts/josh/josh-commands-development.ts +8 -15
  87. package/scripts/josh/josh-commands-project.ts +14 -1
  88. package/scripts/josh/josh-logic.ts +13 -0
  89. package/scripts/josh/run-github-prerequisite.ts +32 -0
  90. package/scripts/lane/lane-await.ts +8 -1
  91. package/scripts/lane/openai-lane-ship.ts +30 -0
  92. package/scripts/lane/openai-lane-supervisor.ts +35 -8
  93. package/scripts/lane/openai-review-broker.ts +260 -0
  94. package/scripts/lint/cspell-cached.ts +21 -0
  95. package/scripts/lint/format.ts +68 -0
  96. package/scripts/lint/lint-parallel.ts +30 -1
  97. package/scripts/observations/observations-flush.ts +1 -1
  98. package/scripts/package/production-dependency-graph.ts +68 -0
  99. package/scripts/package/verify-optional-eslint-install.ts +62 -0
  100. package/scripts/release/github-release-cli.ts +11 -0
  101. package/scripts/release/github-release.ts +220 -0
  102. package/scripts/release/publish-tag-cli.ts +8 -0
  103. package/scripts/release/publish-tag.ts +11 -0
  104. package/scripts/release/release-publish.ts +7 -2
  105. package/scripts/rules/delivered-rules.ts +2 -0
  106. package/scripts/rules/issue-bug-label.ts +85 -0
  107. package/scripts/rules/issue-filing-args.ts +50 -0
  108. package/scripts/run/run-merge-cli.ts +19 -0
  109. package/scripts/run/run-merge.ts +9 -1
  110. package/scripts/run/run-ship-cli.ts +20 -0
  111. package/scripts/run/run-ship-detach.ts +180 -14
  112. package/scripts/run/run-ship-review-steps.ts +45 -25
  113. package/scripts/run/run-step-cli.ts +11 -1
  114. package/scripts/run/run-step.ts +36 -8
  115. package/scripts/run/run-wake-session.ts +3 -1
  116. package/scripts/test/pilot-files.ts +0 -2
  117. package/scripts/test/skip-marker.ts +3 -0
  118. package/scripts/test/test-declared-changed.ts +2 -1
  119. package/scripts/test/test-declared-logic.ts +15 -10
  120. package/scripts/test/test-declared.ts +20 -8
  121. package/scripts/test/test-red-logic.ts +3 -3
  122. package/scripts/test/test-unit-guard.ts +24 -12
  123. package/templates/CLAUDE.static.md +8 -0
  124. package/templates/cursorrules.static +1 -0
  125. package/templates/pnpm-workspace.static.yaml +4 -0
  126. package/templates/prettierignore.static +12 -0
  127. 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 is_exempt(path: string): boolean {
40
- if (EXEMPT_PATHS.includes(path)) return true
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>): Verdict {
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>): Array<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>): Array<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 detail_for(verdict: Verdict, paths: ReadonlyArray<string>): string {
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.runtime_files(paths).map((path) => typed_runtime_file(path))
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(paths: ReadonlyArray<string>): { detail: string; verdict: Verdict } {
94
- const verdict = test_declared_logic.verdict_for(paths)
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 { markdown_section } from '#scripts/issue/markdown-section'
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 markdown_section.has_line(body, BUG_DECLARATION_LINE)
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 declaring that it runs unit tests.** Zero matching files
23
- // there is a broken state rather than a young one — a mis-scoped glob, or a suite that was
24
- // deleted — and reporting it as a passing check hands `pnpm josh followup`, which reads
25
- // only the exit code, a verification that verified nothing. That half fails.
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(is_installed: boolean, has_tests: boolean): GuardAction {
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 (!has_tests) return FAIL_ACTION
60
+ if (has_tests) return 'run'
55
61
 
56
- return 'run'
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
- console.info(`josh ${command_label}: ${SKIP_REASON} ${SKIP_MARKER} vitest unit tests.`)
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(is_installed, has_tests)
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.
@@ -0,0 +1,4 @@
1
+ # Written by `josh init` for a static project (joshuafolkken/kit#2693). kit's CLI runs on tsx, which
2
+ # depends on esbuild, and pnpm fails an install whose dependencies carry an unapproved build script.
3
+ allowBuilds:
4
+ esbuild: true
@@ -0,0 +1,12 @@
1
+ # Dependencies and generated files
2
+ node_modules/
3
+ dist/
4
+ build/
5
+ .pnpm-store/
6
+
7
+ # Package manager lock files
8
+ package-lock.json
9
+ pnpm-lock.yaml
10
+ yarn.lock
11
+ bun.lock
12
+ bun.lockb
@@ -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