@joshuafolkken/kit 1.983.0 → 1.1003.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-child.md +4 -5
- package/.claude/skills/workflow-commands/backlogrun-steps.md +1 -1
- package/.github/workflows/sonar-qube.yml +12 -2
- package/CLAUDE.md +2 -2
- package/README.md +30 -21
- package/dist/CLAUDE.md +2 -2
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-ZT65LR3U.js → chunk-4GDR2YTV.js} +4 -4
- package/dist/hooks/{chunk-ASUZGZFT.js → chunk-56LGMJJV.js} +2 -2
- package/dist/hooks/{chunk-LBXQHXMC.js → chunk-6PSTVOLH.js} +263 -74
- package/dist/hooks/{chunk-P3UX7SFN.js → chunk-A2H5I2VD.js} +2 -2
- package/dist/hooks/{chunk-ERXMJJOV.js → chunk-AWTFKKFW.js} +1 -1
- package/dist/hooks/{chunk-IM3KM25Y.js → chunk-EZLHXKJ6.js} +10 -7
- package/dist/hooks/{chunk-APVOACVT.js → chunk-GBJUERYK.js} +1 -1
- package/dist/hooks/{chunk-5JX6HXDX.js → chunk-KPZN42RT.js} +3 -3
- package/dist/hooks/{chunk-47SPMGOD.js → chunk-PAXMLGSS.js} +26 -7
- package/dist/hooks/{chunk-RFPHKU7C.js → chunk-WMIR5SNX.js} +3 -3
- 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 +98 -26
- package/dist/josh.js +5 -5
- package/package.json +5 -1
- package/prettier/basic.d.ts +3 -0
- package/prettier/basic.js +10 -0
- package/prettier/index.js +2 -2
- package/prettier/static.js +3 -10
- package/prompts/sonar-hotspot-handling.md +6 -0
- package/prompts/testing-guide.md +1 -1
- package/scripts/agent/agent-diagnostics.ts +1 -0
- package/scripts/agent/agent-role-profile.ts +1 -1
- package/scripts/behavior/behavior-rules.ts +8 -1
- package/scripts/build/build-claude-md.ts +13 -4
- package/scripts/ci/pr-classification.ts +11 -2
- package/scripts/document/document-byte-budget.ts +1 -1
- package/scripts/gate/gate-steps.ts +1 -1
- package/scripts/gate/project-checks.ts +12 -11
- package/scripts/gate/type-check-command.ts +1 -1
- package/scripts/git/git-branch.ts +14 -3
- package/scripts/git/git-command.ts +5 -2
- package/scripts/git/git-issue.ts +13 -5
- package/scripts/git/git-pr.ts +16 -10
- package/scripts/git/git-preflight.ts +138 -0
- package/scripts/git/telegram-notify.ts +21 -0
- package/scripts/hooks/stop-guard.ts +2 -0
- package/scripts/init/basic-path-migration.ts +24 -0
- package/scripts/init/distributed-paths.ts +22 -0
- package/scripts/init/init-actions.ts +27 -19
- package/scripts/init/init-ai-copy.ts +86 -28
- package/scripts/init/{init-static.ts → init-basic.ts} +6 -6
- package/scripts/init/init-bootstrap.ts +4 -3
- package/scripts/init/init-install.ts +1 -1
- package/scripts/init/init-logic-templates.ts +1 -1
- package/scripts/init/init-logic.ts +1 -1
- package/scripts/init/init.ts +41 -19
- package/scripts/init/kit-setup-state.ts +30 -0
- package/scripts/init/kit-written-paths.ts +33 -0
- package/scripts/init/project-profile-cli.ts +15 -1
- package/scripts/init/project-profile.ts +41 -12
- package/scripts/init/session-language-environment.ts +83 -0
- package/scripts/init/start-exec.ts +28 -0
- package/scripts/init/start-plan.ts +35 -13
- package/scripts/init/start-prompt.ts +22 -8
- package/scripts/init/start-setup-pr.ts +138 -0
- package/scripts/init/start-steps.ts +30 -30
- package/scripts/init/start.ts +7 -3
- package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +3 -3
- package/scripts/josh/josh-commands-development.ts +3 -3
- package/scripts/josh/josh-commands-project.ts +2 -2
- package/scripts/lane/lane-leftover.ts +143 -0
- package/scripts/lane/lane-open.ts +14 -1
- package/scripts/lint/cspell-cached.ts +3 -3
- package/scripts/lint/format.ts +1 -1
- package/scripts/lint/lint-parallel.ts +2 -2
- package/scripts/propagate/propagate-steps.ts +14 -1
- package/scripts/rules/filing-cap.ts +1 -0
- package/scripts/rules/issue-citation.ts +30 -1
- package/scripts/rules/last-prompt.ts +79 -0
- package/scripts/rules/stop-rules.ts +5 -3
- package/scripts/run/run-hold.ts +3 -3
- package/scripts/run/run-preflight.ts +75 -5
- package/scripts/run/run-progress-cli.ts +14 -2
- package/scripts/safe-chain/ci-installer-pin.ts +19 -12
- package/scripts/safe-chain/project-config.ts +1 -1
- package/scripts/sync/sync-ai-files.ts +204 -0
- package/scripts/sync/sync.ts +48 -184
- package/scripts/test/josh-harness-environment.ts +1 -1
- package/scripts/test/pilot-files.ts +0 -1
- package/scripts/test/test-declared-changed.ts +1 -1
- package/scripts/test/test-declared-logic.ts +55 -14
- package/scripts/test/test-declared.ts +23 -11
- package/scripts/test/test-unit-guard.ts +6 -6
- package/scripts/time-runtime/time-shell.ts +36 -2
- package/scripts-ai/git-workflow.ts +10 -0
- package/templates/CLAUDE.basic.md +8 -0
- package/templates/workflows/ci.yml +27 -0
- /package/{templates/CLAUDE.static.md → dist/CLAUDE.basic.md} +0 -0
- /package/templates/{cursorrules.static → cursorrules.basic} +0 -0
- /package/templates/{pnpm-workspace.static.yaml → pnpm-workspace.basic.yaml} +0 -0
- /package/templates/{prettierignore.static → prettierignore.basic} +0 -0
|
@@ -2,12 +2,14 @@ import { readFileSync } from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { json_format } from '#scripts/config-merge/json-format'
|
|
4
4
|
import { string_array_schema, vscode_settings_schema } from '#scripts/lib/schemas'
|
|
5
|
+
import { basic_path_migration } from './basic-path-migration'
|
|
5
6
|
import { init_logic } from './init-logic'
|
|
6
7
|
import { package_path, PROJECT_ROOT } from './init-paths'
|
|
7
8
|
import type { ProjectShape } from './project-profile'
|
|
9
|
+
import { session_language_environment } from './session-language-environment'
|
|
8
10
|
|
|
9
11
|
const PRETTIER_CONFIG_JS = 'prettier.config.js'
|
|
10
|
-
const
|
|
12
|
+
const BASIC_PRETTIER_CONFIG = 'prettier.config.mjs'
|
|
11
13
|
const VSCODE_EXTENSIONS_PATH = '.vscode/extensions.json'
|
|
12
14
|
const VSCODE_SETTINGS_PATH = '.vscode/settings.json'
|
|
13
15
|
const TSCONFIG_PATH = 'tsconfig.json'
|
|
@@ -17,6 +19,9 @@ interface FileAction {
|
|
|
17
19
|
dest: string
|
|
18
20
|
create: () => string
|
|
19
21
|
merge?: (existing: string) => string
|
|
22
|
+
// A merge that only migrates a kit-written line, for a file kit otherwise leaves to the project:
|
|
23
|
+
// when it changes nothing, the file is reported with the sample to add, as one with no merge is.
|
|
24
|
+
should_show_sample_when_unchanged?: boolean
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
function read_package_file(relative_path: string): string {
|
|
@@ -59,7 +64,7 @@ function build_vscode_actions(): ReadonlyArray<FileAction> {
|
|
|
59
64
|
]
|
|
60
65
|
}
|
|
61
66
|
|
|
62
|
-
const
|
|
67
|
+
const BASIC_EXTENSIONS = [
|
|
63
68
|
'streetsidesoftware.code-spell-checker',
|
|
64
69
|
'pkief.material-icon-theme',
|
|
65
70
|
'usernamehw.errorlens',
|
|
@@ -69,16 +74,16 @@ const STATIC_EXTENSIONS = [
|
|
|
69
74
|
const PRETTIER_EXTENSION = 'esbenp.prettier-vscode'
|
|
70
75
|
const DEFAULT_FORMATTER = 'editor.defaultFormatter'
|
|
71
76
|
const FORMAT_ON_SAVE = 'editor.formatOnSave'
|
|
72
|
-
const
|
|
77
|
+
const BASIC_LANGUAGES = ['[html]', '[css]', '[javascript]']
|
|
73
78
|
|
|
74
|
-
function
|
|
79
|
+
function basic_settings(): Record<string, Record<string, unknown>> {
|
|
75
80
|
const formatter = { [DEFAULT_FORMATTER]: PRETTIER_EXTENSION, [FORMAT_ON_SAVE]: true }
|
|
76
81
|
|
|
77
|
-
return Object.fromEntries(
|
|
82
|
+
return Object.fromEntries(BASIC_LANGUAGES.map((language) => [language, formatter]))
|
|
78
83
|
}
|
|
79
84
|
|
|
80
|
-
function
|
|
81
|
-
const settings =
|
|
85
|
+
function build_basic_settings_action(): FileAction {
|
|
86
|
+
const settings = basic_settings()
|
|
82
87
|
|
|
83
88
|
return build_action(
|
|
84
89
|
VSCODE_SETTINGS_PATH,
|
|
@@ -87,10 +92,10 @@ function build_static_settings_action(): FileAction {
|
|
|
87
92
|
)
|
|
88
93
|
}
|
|
89
94
|
|
|
90
|
-
function
|
|
95
|
+
function build_basic_vscode_actions(shape: ProjectShape): ReadonlyArray<FileAction> {
|
|
91
96
|
const recommendations = shape.has_web
|
|
92
|
-
? [...
|
|
93
|
-
:
|
|
97
|
+
? [...BASIC_EXTENSIONS, PRETTIER_EXTENSION]
|
|
98
|
+
: BASIC_EXTENSIONS
|
|
94
99
|
const actions: Array<FileAction> = [
|
|
95
100
|
build_action(
|
|
96
101
|
VSCODE_EXTENSIONS_PATH,
|
|
@@ -99,12 +104,12 @@ function build_static_vscode_actions(shape: ProjectShape): ReadonlyArray<FileAct
|
|
|
99
104
|
),
|
|
100
105
|
]
|
|
101
106
|
|
|
102
|
-
if (shape.has_web) actions.push(
|
|
107
|
+
if (shape.has_web) actions.push(build_basic_settings_action())
|
|
103
108
|
|
|
104
109
|
return actions
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
function
|
|
112
|
+
function build_basic_tsconfig_action(): FileAction {
|
|
108
113
|
return build_action(
|
|
109
114
|
TSCONFIG_PATH,
|
|
110
115
|
() => json_format.format_json({ extends: init_logic.get_tsconfig_extends_entry() }),
|
|
@@ -187,26 +192,28 @@ function build_gitignore_action(): FileAction {
|
|
|
187
192
|
)
|
|
188
193
|
}
|
|
189
194
|
|
|
190
|
-
function
|
|
195
|
+
function build_basic_actions(shape: ProjectShape): ReadonlyArray<FileAction> {
|
|
191
196
|
const actions: Array<FileAction> = []
|
|
192
197
|
if (shape.has_git) actions.push(build_gitignore_action())
|
|
193
|
-
if (shape.has_typescript) actions.push(
|
|
198
|
+
if (shape.has_typescript) actions.push(build_basic_tsconfig_action())
|
|
194
199
|
|
|
195
200
|
if (shape.has_web) {
|
|
196
201
|
actions.push({
|
|
197
|
-
dest:
|
|
202
|
+
dest: BASIC_PRETTIER_CONFIG,
|
|
198
203
|
create: () =>
|
|
199
|
-
"import { config } from '@joshuafolkken/kit/prettier/
|
|
204
|
+
"import { config } from '@joshuafolkken/kit/prettier/basic'\n\nexport default config\n",
|
|
205
|
+
merge: (existing) => basic_path_migration.migrate_basic_paths(existing),
|
|
206
|
+
should_show_sample_when_unchanged: true,
|
|
200
207
|
})
|
|
201
208
|
}
|
|
202
209
|
|
|
203
|
-
actions.push(...
|
|
210
|
+
actions.push(...build_basic_vscode_actions(shape))
|
|
204
211
|
|
|
205
212
|
return actions
|
|
206
213
|
}
|
|
207
214
|
|
|
208
215
|
function build_file_actions(shape?: ProjectShape): ReadonlyArray<FileAction> {
|
|
209
|
-
if (shape?.profile === '
|
|
216
|
+
if (shape?.profile === 'basic') return build_basic_actions(shape)
|
|
210
217
|
|
|
211
218
|
return [
|
|
212
219
|
...(shape?.has_git === false ? [] : [build_gitignore_action()]),
|
|
@@ -226,10 +233,11 @@ function build_file_actions(shape?: ProjectShape): ReadonlyArray<FileAction> {
|
|
|
226
233
|
...build_config_file_actions().filter(
|
|
227
234
|
(action) => shape?.has_git !== false || action.dest !== LEFTHOOK_PATH,
|
|
228
235
|
),
|
|
236
|
+
...session_language_environment.build_session_lang_actions(),
|
|
229
237
|
]
|
|
230
238
|
}
|
|
231
239
|
|
|
232
240
|
const init_actions = { build_file_actions, read_package_json }
|
|
233
241
|
|
|
234
242
|
export type { FileAction }
|
|
235
|
-
export { init_actions, PRETTIER_CONFIG_JS }
|
|
243
|
+
export { init_actions, PRETTIER_CONFIG_JS, BASIC_PRETTIER_CONFIG }
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
copy_directory_failure,
|
|
9
9
|
directory_copy_blocker,
|
|
10
10
|
} from '#scripts/sync/directory-copy-guard'
|
|
11
|
+
import { basic_path_migration } from './basic-path-migration'
|
|
12
|
+
import { distributed_paths } from './distributed-paths'
|
|
11
13
|
import { transform_copied_content } from './init-copy-content'
|
|
12
14
|
import { init_logic } from './init-logic'
|
|
13
15
|
import { package_path, PROJECT_ROOT } from './init-paths'
|
|
@@ -16,12 +18,12 @@ import type { ProjectShape } from './project-profile'
|
|
|
16
18
|
|
|
17
19
|
const WORKSPACE_YAML = 'pnpm-workspace.yaml'
|
|
18
20
|
const CLAUDE_MD_FILENAME = 'CLAUDE.md'
|
|
19
|
-
const
|
|
21
|
+
const BASIC_CLAUDE_IMPORT = '@node_modules/@joshuafolkken/kit/dist/CLAUDE.basic.md'
|
|
20
22
|
const PRETTIER_IGNORE = '.prettierignore'
|
|
21
23
|
const GIT_ATTRIBUTES = '.gitattributes'
|
|
22
24
|
const CODE_OF_CONDUCT = 'CODE_OF_CONDUCT.md'
|
|
23
25
|
const SECURITY_MD = 'SECURITY.md'
|
|
24
|
-
const
|
|
26
|
+
const BASIC_AI_FILES = [
|
|
25
27
|
'AGENTS.md',
|
|
26
28
|
'GEMINI.md',
|
|
27
29
|
PRETTIER_IGNORE,
|
|
@@ -30,16 +32,16 @@ const STATIC_AI_FILES = [
|
|
|
30
32
|
SECURITY_MD,
|
|
31
33
|
WORKSPACE_YAML,
|
|
32
34
|
]
|
|
33
|
-
// A
|
|
34
|
-
const
|
|
35
|
-
[PRETTIER_IGNORE]: 'templates/prettierignore.
|
|
36
|
-
[WORKSPACE_YAML]: 'templates/pnpm-workspace.
|
|
35
|
+
// A basic project gets its own template for these, not kit's development copy (joshuafolkken/kit#2693).
|
|
36
|
+
const BASIC_SOURCES: Readonly<Record<string, string>> = {
|
|
37
|
+
[PRETTIER_IGNORE]: 'templates/prettierignore.basic',
|
|
38
|
+
[WORKSPACE_YAML]: 'templates/pnpm-workspace.basic.yaml',
|
|
37
39
|
}
|
|
38
40
|
const GITHUB_FILES = new Set([CODE_OF_CONDUCT, SECURITY_MD])
|
|
39
|
-
const
|
|
41
|
+
const BASIC_POINTER_MAPPING = { src: 'templates/cursorrules.basic', dest: '.cursorrules' }
|
|
40
42
|
const PULL_REQUEST_TEMPLATE = '.github/pull_request_template.md'
|
|
41
43
|
const RELEASE_CONFIG = '.github/release.yml'
|
|
42
|
-
const
|
|
44
|
+
const BASIC_GITHUB_MAPPINGS = [
|
|
43
45
|
{ src: PULL_REQUEST_TEMPLATE, dest: PULL_REQUEST_TEMPLATE },
|
|
44
46
|
{ src: RELEASE_CONFIG, dest: RELEASE_CONFIG },
|
|
45
47
|
]
|
|
@@ -117,9 +119,16 @@ function did_skip_workspace_yaml_copy(source_path: string, destination_path: str
|
|
|
117
119
|
return false
|
|
118
120
|
}
|
|
119
121
|
|
|
122
|
+
// The package file an AI file is copied from: a basic project gets its own template for a few of
|
|
123
|
+
// them, every other file is copied from the path it is written to.
|
|
124
|
+
function ai_file_source(filename: string, shape?: ProjectShape): string {
|
|
125
|
+
const basic_source = shape?.profile === 'basic' ? BASIC_SOURCES[filename] : undefined
|
|
126
|
+
|
|
127
|
+
return basic_source ?? filename
|
|
128
|
+
}
|
|
129
|
+
|
|
120
130
|
function did_skip_ai_file_copy(filename: string, shape?: ProjectShape): boolean {
|
|
121
|
-
const
|
|
122
|
-
const source_path = package_path(static_source ?? filename)
|
|
131
|
+
const source_path = package_path(ai_file_source(filename, shape))
|
|
123
132
|
const destination_path = path.join(PROJECT_ROOT, filename)
|
|
124
133
|
|
|
125
134
|
if (filename === WORKSPACE_YAML) {
|
|
@@ -212,19 +221,61 @@ function did_skip_ai_directory_copy(directory_name: string): boolean {
|
|
|
212
221
|
// AI file — leaves an existing one untouched so a consumer's additions are never disturbed. `josh sync`
|
|
213
222
|
// is what ensures the import line on an existing file (sync.ts). CLAUDE.md is no longer byte-copied
|
|
214
223
|
// (joshuafolkken/kit#1878), so it is handled here rather than through AI_COPY_FILES.
|
|
224
|
+
//
|
|
225
|
+
// The one edit made to an existing file is the rules import written before joshuafolkken/kit#2829,
|
|
226
|
+
// moved onto the basic path — only that kit-written line, never the consumer's additions.
|
|
227
|
+
// An unreadable file is left as it is and reported as skipped, as it was before the migration.
|
|
228
|
+
function read_if_readable(destination_path: string): string | undefined {
|
|
229
|
+
try {
|
|
230
|
+
return readFileSync(destination_path, 'utf8')
|
|
231
|
+
} catch {
|
|
232
|
+
return undefined
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function report_existing_claude_md(destination_path: string): void {
|
|
237
|
+
const existing = read_if_readable(destination_path) ?? ''
|
|
238
|
+
const migrated = basic_path_migration.migrate_basic_paths(existing)
|
|
239
|
+
|
|
240
|
+
if (migrated === existing) {
|
|
241
|
+
console.info(` ⏭ skipped ${CLAUDE_MD_FILENAME} (already exists)`)
|
|
242
|
+
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
writeFileSync(destination_path, migrated)
|
|
247
|
+
console.info(` ✔ updated ${CLAUDE_MD_FILENAME} (rules import moved to the basic path)`)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function basic_claude_md(): string {
|
|
251
|
+
return `> If kit is not installed, run \`pnpm install\` first.\n\n${BASIC_CLAUDE_IMPORT}\n`
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// A basic CLAUDE.md imports the basic rules alone: the full bootstrap and import are removed, a
|
|
255
|
+
// pre-rename path is moved, and the basic header is restored when no basic import is left
|
|
256
|
+
// (joshuafolkken/kit#2827). The project's own lines stay as they are.
|
|
257
|
+
function ensure_basic_claude_md(existing: string | undefined): string {
|
|
258
|
+
if (existing === undefined) return basic_claude_md()
|
|
259
|
+
const kept = basic_path_migration.migrate_basic_paths(
|
|
260
|
+
distributed_paths.remove_claude_md_import(existing),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
if (kept.includes(BASIC_CLAUDE_IMPORT)) return kept
|
|
264
|
+
|
|
265
|
+
return kept === '' ? basic_claude_md() : `${basic_claude_md()}\n${kept}`
|
|
266
|
+
}
|
|
267
|
+
|
|
215
268
|
function did_skip_claude_md_import(shape?: ProjectShape): boolean {
|
|
216
269
|
const destination_path = path.join(PROJECT_ROOT, CLAUDE_MD_FILENAME)
|
|
217
270
|
|
|
218
271
|
if (existsSync(destination_path)) {
|
|
219
|
-
|
|
272
|
+
report_existing_claude_md(destination_path)
|
|
220
273
|
|
|
221
274
|
return true
|
|
222
275
|
}
|
|
223
276
|
|
|
224
277
|
const content =
|
|
225
|
-
shape?.profile === '
|
|
226
|
-
? `> If kit is not installed, run \`pnpm install\` first.\n\n${STATIC_CLAUDE_IMPORT}\n`
|
|
227
|
-
: init_logic.ensure_claude_md_import(undefined)
|
|
278
|
+
shape?.profile === 'basic' ? basic_claude_md() : init_logic.ensure_claude_md_import(undefined)
|
|
228
279
|
|
|
229
280
|
writeFileSync(destination_path, content)
|
|
230
281
|
console.info(` ✔ created ${CLAUDE_MD_FILENAME}`)
|
|
@@ -251,8 +302,8 @@ function should_copy_github_file(filename: string, shape?: ProjectShape): boolea
|
|
|
251
302
|
return !GITHUB_FILES.has(filename) && !filename.startsWith('.github/')
|
|
252
303
|
}
|
|
253
304
|
|
|
254
|
-
function
|
|
255
|
-
if (filename !== PRETTIER_IGNORE || shape?.profile !== '
|
|
305
|
+
function should_copy_basic_file(filename: string, shape?: ProjectShape): boolean {
|
|
306
|
+
if (filename !== PRETTIER_IGNORE || shape?.profile !== 'basic') return true
|
|
256
307
|
|
|
257
308
|
return shape.has_web
|
|
258
309
|
}
|
|
@@ -261,35 +312,39 @@ function should_copy_ai_file(filename: string, shape?: ProjectShape): boolean {
|
|
|
261
312
|
return (
|
|
262
313
|
should_copy_git_file(filename, shape) &&
|
|
263
314
|
should_copy_github_file(filename, shape) &&
|
|
264
|
-
|
|
315
|
+
should_copy_basic_file(filename, shape)
|
|
265
316
|
)
|
|
266
317
|
}
|
|
267
318
|
|
|
268
319
|
function ai_files(shape?: ProjectShape): ReadonlyArray<string> {
|
|
269
|
-
const files = shape?.profile === '
|
|
320
|
+
const files = shape?.profile === 'basic' ? BASIC_AI_FILES : init_logic.get_ai_copy_files()
|
|
270
321
|
|
|
271
322
|
return files.filter((filename) => should_copy_ai_file(filename, shape))
|
|
272
323
|
}
|
|
273
324
|
|
|
274
|
-
|
|
325
|
+
type FileMappings = ReturnType<typeof init_logic.get_ai_copy_file_mappings>
|
|
326
|
+
|
|
327
|
+
function ai_file_mappings(shape?: ProjectShape): FileMappings {
|
|
275
328
|
const mappings =
|
|
276
|
-
shape?.profile === '
|
|
277
|
-
? [
|
|
329
|
+
shape?.profile === 'basic'
|
|
330
|
+
? [BASIC_POINTER_MAPPING, ...(shape.has_github ? BASIC_GITHUB_MAPPINGS : [])]
|
|
278
331
|
: init_logic.get_ai_copy_file_mappings()
|
|
279
332
|
|
|
280
|
-
return mappings
|
|
281
|
-
|
|
282
|
-
|
|
333
|
+
return mappings.filter(({ dest }) => shape?.has_github !== false || !dest.startsWith('.github/'))
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function ai_mapping_skips(shape?: ProjectShape): ReadonlyArray<boolean> {
|
|
337
|
+
return ai_file_mappings(shape).map(({ src, dest }) => did_skip_ai_file_mapping(src, dest))
|
|
283
338
|
}
|
|
284
339
|
|
|
285
340
|
function repository_name(shape?: ProjectShape): string | undefined {
|
|
286
|
-
if (shape?.has_github === false || shape?.profile === '
|
|
341
|
+
if (shape?.has_github === false || shape?.profile === 'basic') return undefined
|
|
287
342
|
|
|
288
343
|
return gh_spawn.get_repo_name_with_owner()
|
|
289
344
|
}
|
|
290
345
|
|
|
291
346
|
function report_sync_hint(has_skips: boolean, shape?: ProjectShape): void {
|
|
292
|
-
if (!has_skips || shape?.profile === '
|
|
347
|
+
if (!has_skips || shape?.profile === 'basic') return
|
|
293
348
|
|
|
294
349
|
console.info('\n 💡 Run `josh sync` to overwrite skipped AI files with the latest version.')
|
|
295
350
|
}
|
|
@@ -307,7 +362,7 @@ function run_ai_copies(shape?: ProjectShape): string | undefined {
|
|
|
307
362
|
|
|
308
363
|
const name_with_owner = repository_name(shape)
|
|
309
364
|
|
|
310
|
-
if (shape?.profile !== '
|
|
365
|
+
if (shape?.profile !== 'basic') init_sonar.copy_sonar_with_template(name_with_owner)
|
|
311
366
|
|
|
312
367
|
report_sync_hint(has_skips, shape)
|
|
313
368
|
|
|
@@ -318,6 +373,9 @@ const init_ai_copy = {
|
|
|
318
373
|
copy_ai_file,
|
|
319
374
|
run_ai_copies,
|
|
320
375
|
ai_files,
|
|
376
|
+
ai_file_source,
|
|
377
|
+
ai_file_mappings,
|
|
378
|
+
ensure_basic_claude_md,
|
|
321
379
|
}
|
|
322
380
|
|
|
323
|
-
export { init_ai_copy }
|
|
381
|
+
export { init_ai_copy, CLAUDE_MD_FILENAME }
|
|
@@ -10,7 +10,7 @@ function is_record(value: unknown): value is Record<string, unknown> {
|
|
|
10
10
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
interface
|
|
13
|
+
interface BasicVersions {
|
|
14
14
|
kit: string
|
|
15
15
|
prettier: string
|
|
16
16
|
}
|
|
@@ -30,10 +30,10 @@ function with_recorded_profile(content: string, profile: ProjectShape['profile']
|
|
|
30
30
|
return json_format.format_json(manifest)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
function
|
|
33
|
+
function merge_basic_manifest(
|
|
34
34
|
content: string,
|
|
35
35
|
shape: ProjectShape,
|
|
36
|
-
versions:
|
|
36
|
+
versions: BasicVersions,
|
|
37
37
|
): string {
|
|
38
38
|
const with_profile = with_recorded_profile(content, shape.profile)
|
|
39
39
|
const with_script = init_logic.merge_package_scripts(with_profile, {
|
|
@@ -50,6 +50,6 @@ function merge_static_manifest(
|
|
|
50
50
|
return init_logic.sort_package_json_keys(with_prettier)
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const
|
|
54
|
-
export {
|
|
55
|
-
export type {
|
|
53
|
+
const init_basic = { initial_manifest, with_recorded_profile, merge_basic_manifest }
|
|
54
|
+
export { init_basic }
|
|
55
|
+
export type { BasicVersions }
|
|
@@ -3,13 +3,14 @@ import path from 'node:path'
|
|
|
3
3
|
import { z } from 'zod'
|
|
4
4
|
import { init_install, type InstallStep } from './init-install'
|
|
5
5
|
import { init_logic_workspace } from './init-logic-workspace'
|
|
6
|
+
import { project_profile } from './project-profile'
|
|
6
7
|
|
|
7
8
|
const KIT_PACKAGE_NAME = '@joshuafolkken/kit'
|
|
8
9
|
const NODE_MODULES = 'node_modules'
|
|
9
10
|
const PACKAGE_JSON = 'package.json'
|
|
10
|
-
// The
|
|
11
|
+
// The basic template lists exactly the build scripts kit's own dependency tree carries, which is
|
|
11
12
|
// what installing kit alone needs approved (joshuafolkken/kit#2785).
|
|
12
|
-
const KIT_BUILDS_TEMPLATE = 'templates/pnpm-workspace.
|
|
13
|
+
const KIT_BUILDS_TEMPLATE = 'templates/pnpm-workspace.basic.yaml'
|
|
13
14
|
const HANDOFF_FAILURE = 'josh init from the project-installed kit failed — see the output above'
|
|
14
15
|
// Set on the handed-off run, so a kit that still does not find itself installed in the project — pnpm
|
|
15
16
|
// put it somewhere else — stops instead of installing and handing off again without end.
|
|
@@ -62,7 +63,7 @@ function kit_install_step(has_kit: boolean, template: string): InstallStep {
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
function handoff_step(args: ReadonlyArray<string>): InstallStep {
|
|
65
|
-
const handoff_args = ['exec', 'josh', 'init', ...args]
|
|
66
|
+
const handoff_args = ['exec', 'josh', 'init', ...project_profile.with_legacy_profile_names(args)]
|
|
66
67
|
|
|
67
68
|
return {
|
|
68
69
|
label: `pnpm ${handoff_args.join(' ')}`,
|
|
@@ -51,7 +51,7 @@ function did_step_succeed(step: InstallStep, project_root: string): boolean {
|
|
|
51
51
|
|
|
52
52
|
// Returns the failure to report, or undefined once the install passed. A failed install stops
|
|
53
53
|
// there: `josh format` runs prettier and the kit itself, both of which the install provides. A failed
|
|
54
|
-
// format does not fail `init` — in a
|
|
54
|
+
// format does not fail `init` — in a full project it also runs `eslint --fix`, which exits non-zero on
|
|
55
55
|
// the project's own unfixable errors, and those are the project's result, not the setup's (the
|
|
56
56
|
// reason `josh gate` is left out of `init` too).
|
|
57
57
|
function run_post_init_steps(project_root: string): string | undefined {
|
|
@@ -132,7 +132,7 @@ ${stylesheet_line}}
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// A named stylesheet is kept only while it exists: kit ≤1.929 wrote the SvelteKit default into every
|
|
135
|
-
//
|
|
135
|
+
// full project, and keeping that line would leave `josh sync` unable to repair the aborting format.
|
|
136
136
|
function existing_stylesheet(existing: string, project_root: string): string | undefined {
|
|
137
137
|
const named = TAILWIND_STYLESHEET_PATTERN.exec(existing)?.[1]
|
|
138
138
|
|
|
@@ -230,7 +230,7 @@ const PRETTIER_PLUGIN_DEV_DEPS: Record<string, string> = {
|
|
|
230
230
|
// format_json rather than JSON.stringify: it lays arrays out the way prettier does — inline while
|
|
231
231
|
// they fit within printWidth, one element per line once they do not — so the file kit writes is
|
|
232
232
|
// `prettier --check`-clean in the consumer whatever the entry count happens to be (#660).
|
|
233
|
-
// TypeScript 6 defaults `types` to `[]`, so a
|
|
233
|
+
// TypeScript 6 defaults `types` to `[]`, so a full project sees no `process` global — not even in the
|
|
234
234
|
// generated `playwright.config.ts` — until the Node types are named (joshuafolkken/kit#2710).
|
|
235
235
|
function generate_tsconfig(): string {
|
|
236
236
|
return json_format.format_json({
|
package/scripts/init/init.ts
CHANGED
|
@@ -15,11 +15,12 @@ import { execaSync } from 'execa'
|
|
|
15
15
|
import { z } from 'zod'
|
|
16
16
|
import { init_actions, PRETTIER_CONFIG_JS, type FileAction } from './init-actions'
|
|
17
17
|
import { init_ai_copy } from './init-ai-copy'
|
|
18
|
+
import { init_basic } from './init-basic'
|
|
18
19
|
import { init_bootstrap } from './init-bootstrap'
|
|
19
20
|
import { init_install } from './init-install'
|
|
20
21
|
import { init_logic } from './init-logic'
|
|
21
22
|
import { PACKAGE_DIR, PROJECT_ROOT } from './init-paths'
|
|
22
|
-
import {
|
|
23
|
+
import { kit_setup_state } from './kit-setup-state'
|
|
23
24
|
import { plugin_install_hint_module } from './plugin-install-hint'
|
|
24
25
|
import { project_profile, type ProjectProfile, type ProjectShape } from './project-profile'
|
|
25
26
|
|
|
@@ -30,7 +31,7 @@ const SAMPLE_INDENT_WIDTH = 4
|
|
|
30
31
|
const ARGUMENT_START_INDEX = 2
|
|
31
32
|
const SAMPLE_INDENT = ' '.repeat(SAMPLE_INDENT_WIDTH)
|
|
32
33
|
const INSTALL_HINT = '→ run `pnpm install` to install what package.json lists'
|
|
33
|
-
const INIT_USAGE = 'josh init [--profile
|
|
34
|
+
const INIT_USAGE = 'josh init [--profile basic|full] [--no-install]'
|
|
34
35
|
|
|
35
36
|
function write_new_file(action: FileAction, destination_path: string): void {
|
|
36
37
|
mkdirSync(path.dirname(destination_path), { recursive: true })
|
|
@@ -44,22 +45,27 @@ function show_sample(action: FileAction): void {
|
|
|
44
45
|
console.info(action.create().replaceAll(/^/gmu, () => SAMPLE_INDENT))
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
function report_unchanged(action: FileAction): void {
|
|
49
|
+
if (action.should_show_sample_when_unchanged === true) show_sample(action)
|
|
50
|
+
else console.info(` ✔ unchanged ${action.dest}`)
|
|
51
|
+
}
|
|
52
|
+
|
|
47
53
|
function merge_existing_file(
|
|
48
54
|
merge_function: (existing: string) => string,
|
|
49
55
|
destination_path: string,
|
|
50
|
-
|
|
56
|
+
action: FileAction,
|
|
51
57
|
): void {
|
|
52
58
|
const existing = readFileSync(destination_path, 'utf8')
|
|
53
59
|
const merged = merge_function(existing)
|
|
54
60
|
|
|
55
61
|
if (merged === existing) {
|
|
56
|
-
|
|
62
|
+
report_unchanged(action)
|
|
57
63
|
|
|
58
64
|
return
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
writeFileSync(destination_path, merged)
|
|
62
|
-
console.info(` ✔ updated ${
|
|
68
|
+
console.info(` ✔ updated ${action.dest}`)
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
function execute_file_action(action: FileAction): void {
|
|
@@ -78,7 +84,7 @@ function execute_file_action(action: FileAction): void {
|
|
|
78
84
|
return
|
|
79
85
|
}
|
|
80
86
|
|
|
81
|
-
merge_existing_file(action.merge, destination_path, action
|
|
87
|
+
merge_existing_file(action.merge, destination_path, action)
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
const engine_pin_schema = z.looseObject({ name: z.string(), version: z.string() })
|
|
@@ -181,7 +187,7 @@ function apply_package_json_merges(content: string, has_git = true): string {
|
|
|
181
187
|
return package_manager_version.align_development_engines_version(sorted)
|
|
182
188
|
}
|
|
183
189
|
|
|
184
|
-
function
|
|
190
|
+
function get_basic_versions(): { kit: string; prettier: string } {
|
|
185
191
|
const manifest = z
|
|
186
192
|
.object({
|
|
187
193
|
version: z.string(),
|
|
@@ -195,11 +201,11 @@ function get_static_versions(): { kit: string; prettier: string } {
|
|
|
195
201
|
}
|
|
196
202
|
|
|
197
203
|
function merged_manifest(existing: string, shape: ProjectShape): string {
|
|
198
|
-
if (shape.profile === '
|
|
199
|
-
return
|
|
204
|
+
if (shape.profile === 'basic') {
|
|
205
|
+
return init_basic.merge_basic_manifest(existing, shape, get_basic_versions())
|
|
200
206
|
}
|
|
201
207
|
|
|
202
|
-
return
|
|
208
|
+
return init_basic.with_recorded_profile(
|
|
203
209
|
apply_package_json_merges(existing, shape.has_git),
|
|
204
210
|
shape.profile,
|
|
205
211
|
)
|
|
@@ -210,7 +216,7 @@ function merge_project_package_json(shape: ProjectShape): void {
|
|
|
210
216
|
const is_existing = existsSync(package_json_path)
|
|
211
217
|
const existing = is_existing
|
|
212
218
|
? readFileSync(package_json_path, 'utf8')
|
|
213
|
-
:
|
|
219
|
+
: init_basic.initial_manifest()
|
|
214
220
|
const merged = merged_manifest(existing, shape)
|
|
215
221
|
|
|
216
222
|
if (is_existing && merged === existing) {
|
|
@@ -246,7 +252,7 @@ function run_config_file_actions(shape: ProjectShape): void {
|
|
|
246
252
|
console.info('Config files:')
|
|
247
253
|
|
|
248
254
|
if (
|
|
249
|
-
shape.profile === '
|
|
255
|
+
shape.profile === 'full' &&
|
|
250
256
|
sync.migrate_prettierrc(path.join(PROJECT_ROOT, PRETTIER_CONFIG_JS))
|
|
251
257
|
) {
|
|
252
258
|
console.info(' ✔ migrated .prettierrc → prettier.config.js')
|
|
@@ -286,7 +292,7 @@ function run_ai_file_actions(shape: ProjectShape): void {
|
|
|
286
292
|
|
|
287
293
|
if (shape.has_github) report_repository_settings(name_with_owner)
|
|
288
294
|
|
|
289
|
-
if (shape.profile === '
|
|
295
|
+
if (shape.profile === 'full') plugin_install_hint_module.report_plugin_install_hint()
|
|
290
296
|
}
|
|
291
297
|
|
|
292
298
|
function initialize_project(shape: ProjectShape): void {
|
|
@@ -298,14 +304,14 @@ function initialize_project(shape: ProjectShape): void {
|
|
|
298
304
|
merge_project_package_json(shape)
|
|
299
305
|
|
|
300
306
|
run_ai_file_actions(shape)
|
|
301
|
-
if (shape.profile === '
|
|
307
|
+
if (shape.profile === 'full') project_config.sync_project_config(PROJECT_ROOT)
|
|
302
308
|
}
|
|
303
309
|
|
|
304
310
|
// A tool `init` just listed is absent until the user installs it — and `josh lint` then fails on it
|
|
305
311
|
// rather than skipping it (joshuafolkken/kit#2709). Saying so is what tells the user the next step.
|
|
306
312
|
function report_manual_install(shape: ProjectShape): void {
|
|
307
313
|
console.info(`\nDependencies:\n ${INSTALL_HINT}`)
|
|
308
|
-
if (shape.has_git && shape.profile === '
|
|
314
|
+
if (shape.has_git && shape.profile === 'full') install_lefthook()
|
|
309
315
|
}
|
|
310
316
|
|
|
311
317
|
// The install runs the `prepare` script, which installs the git hooks. A failure is thrown rather
|
|
@@ -340,18 +346,34 @@ function set_up_project(requested: ProjectProfile | undefined, is_install: boole
|
|
|
340
346
|
console.info('\n✅ Done.\n')
|
|
341
347
|
}
|
|
342
348
|
|
|
343
|
-
|
|
344
|
-
|
|
349
|
+
// True when this process set the project up — false when it refused, or handed the run to the
|
|
350
|
+
// project's own kit, which then speaks for itself.
|
|
351
|
+
function main(args: ReadonlyArray<string> = []): boolean {
|
|
352
|
+
if (did_refuse_self_run(PACKAGE_DIR, PROJECT_ROOT)) return false
|
|
345
353
|
const { is_install, rest } = init_install.split_install_flag(args)
|
|
346
354
|
// Validated before the hand-off, so a malformed invocation is refused before kit is installed.
|
|
347
355
|
const requested = project_profile.requested_profile(rest, INIT_USAGE)
|
|
348
356
|
|
|
349
|
-
|
|
357
|
+
const is_set_up_here = !is_install || !did_hand_off(args)
|
|
358
|
+
|
|
359
|
+
if (is_set_up_here) set_up_project(requested, is_install)
|
|
360
|
+
|
|
361
|
+
return is_set_up_here
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Only the command a person typed points onward to `josh start`: `josh start` calls `main` itself and
|
|
365
|
+
// is already the next step (joshuafolkken/kit#2816).
|
|
366
|
+
// A setup that failed after writing, such as a failed install, sets the exit code and points nowhere.
|
|
367
|
+
function print_start_hint(): void {
|
|
368
|
+
if (process.exitCode !== undefined && process.exitCode !== 0) return
|
|
369
|
+
const hint = kit_setup_state.start_hint(PROJECT_ROOT)
|
|
370
|
+
|
|
371
|
+
if (hint !== undefined) console.info(`${hint}\n`)
|
|
350
372
|
}
|
|
351
373
|
|
|
352
374
|
function run_cli(args: ReadonlyArray<string>): void {
|
|
353
375
|
try {
|
|
354
|
-
main(args)
|
|
376
|
+
if (main(args)) print_start_hint()
|
|
355
377
|
} catch (error) {
|
|
356
378
|
console.error(`\n✖ ${error instanceof Error ? error.message : String(error)}\n`)
|
|
357
379
|
process.exitCode = 1
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { KIT_PACKAGE_NAME } from '#scripts/version/kit-descriptor'
|
|
2
|
+
import { start_exec } from './start-exec'
|
|
3
|
+
|
|
4
|
+
const START_HINT =
|
|
5
|
+
'To use the GitHub Issue workflow, run pnpm exec josh start next: it puts this setup on GitHub, through a pull request when main already has commits.'
|
|
6
|
+
|
|
7
|
+
// Whether the commit checked out records kit in its manifest — the sign that kit's setup has already
|
|
8
|
+
// reached the branch, so `josh start` has no setup pull request to open (joshuafolkken/kit#2816).
|
|
9
|
+
function is_kit_committed(root: string): boolean {
|
|
10
|
+
// `./` resolves the path from `root` rather than from the repository's top level.
|
|
11
|
+
const manifest = start_exec.read_output('git', ['show', 'HEAD:./package.json'], root)
|
|
12
|
+
|
|
13
|
+
return manifest?.includes(`"${KIT_PACKAGE_NAME}"`) ?? false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function is_git_work_tree(root: string): boolean {
|
|
17
|
+
return start_exec.succeeds('git', ['rev-parse', '--is-inside-work-tree'], root)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// The line `josh init` ends with in a Git repository whose checkout does not hold kit yet: the setup is
|
|
21
|
+
// still uncommitted, and `josh start` is what carries it to GitHub.
|
|
22
|
+
function start_hint(root: string): string | undefined {
|
|
23
|
+
if (!is_git_work_tree(root) || is_kit_committed(root)) return undefined
|
|
24
|
+
|
|
25
|
+
return START_HINT
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const kit_setup_state = { is_kit_committed, start_hint }
|
|
29
|
+
|
|
30
|
+
export { kit_setup_state }
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { PROJECT_CONFIG_FILE } from '#scripts/safe-chain/project-config'
|
|
2
|
+
import { managed_config_scope } from '#scripts/sync/managed-config-scope'
|
|
3
|
+
import { BASIC_PRETTIER_CONFIG } from './init-actions'
|
|
4
|
+
import { CLAUDE_MD_FILENAME } from './init-ai-copy'
|
|
5
|
+
|
|
6
|
+
const MANIFEST_FILE = 'package.json'
|
|
7
|
+
const LOCKFILE = 'pnpm-lock.yaml'
|
|
8
|
+
|
|
9
|
+
// The paths `josh init` writes beyond what `managed_config_scope` enumerates. They stay out of that
|
|
10
|
+
// scope on purpose: it also answers the pull-request gate on distributed config, which leaves
|
|
11
|
+
// CLAUDE.md out by design. The manifest and lockfile are written by the install `josh init` runs.
|
|
12
|
+
const SETUP_ONLY_PATHS: ReadonlySet<string> = new Set([
|
|
13
|
+
CLAUDE_MD_FILENAME,
|
|
14
|
+
BASIC_PRETTIER_CONFIG,
|
|
15
|
+
PROJECT_CONFIG_FILE,
|
|
16
|
+
MANIFEST_FILE,
|
|
17
|
+
LOCKFILE,
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
// Whether a changed path is one kit's setup wrote — what the setup pull request of `josh start`
|
|
21
|
+
// carries, so a file the user keeps beside it (notes, an editor's own directory) never rides along
|
|
22
|
+
// (joshuafolkken/kit#2816).
|
|
23
|
+
function is_kit_written(file_path: string): boolean {
|
|
24
|
+
return SETUP_ONLY_PATHS.has(file_path) || managed_config_scope.has_managed_path([file_path])
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function select(paths: ReadonlyArray<string>): Array<string> {
|
|
28
|
+
return paths.filter((file_path) => is_kit_written(file_path))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const kit_written_paths = { is_kit_written, select }
|
|
32
|
+
|
|
33
|
+
export { kit_written_paths }
|