@joshuafolkken/kit 1.1387.0 → 1.1435.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/epic-commands/SKILL.md +1 -1
- package/.claude/skills/epic-commands/epic-bundle.md +5 -0
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +5 -3
- package/.claude/skills/workflow-commands/backlogrun-steps.md +4 -3
- package/.claude/skills/workflow-commands/backlogrun.md +1 -0
- package/.claude/skills/workflow-commands/chain-rule.md +4 -2
- package/.claude/skills/workflow-commands/issue-comments.md +6 -0
- package/.claude/skills/workflow-commands/observation-ledger.md +3 -2
- package/.claude/skills/workflow-commands/pre-gate-cut.md +5 -3
- package/README.md +25 -0
- package/dist/hooks/audit-provision.js +2 -2
- package/dist/hooks/batch-guard.js +7 -7
- package/dist/hooks/{chunk-IYVOLN6A.js → chunk-2G5F2YCD.js} +3 -3
- package/dist/hooks/{chunk-2YSW3CXH.js → chunk-3LE6VLSR.js} +17 -6
- package/dist/hooks/{chunk-J3KSS7II.js → chunk-4BHUPFKF.js} +1 -1
- package/dist/hooks/{chunk-EAQEGME7.js → chunk-5SJSSCJG.js} +1 -1
- package/dist/hooks/{chunk-FZ6FUSBM.js → chunk-723HKZO5.js} +1 -1
- package/dist/hooks/{chunk-LMWBRJIP.js → chunk-7KMN5QB5.js} +3 -3
- package/dist/hooks/{chunk-NEC4AT4Z.js → chunk-DGT54IH5.js} +4 -0
- package/dist/hooks/{chunk-STDSEPWU.js → chunk-EMB55BDF.js} +4 -4
- package/dist/hooks/{chunk-JBBMAPC4.js → chunk-IX6TTTI6.js} +4 -0
- package/dist/hooks/{chunk-GJ7JPIHN.js → chunk-JXQBK323.js} +2674 -187
- package/dist/hooks/{chunk-DCY2GWHC.js → chunk-MSYSUSY6.js} +5 -5
- package/dist/hooks/{chunk-EBSHN25W.js → chunk-MU4LJFIV.js} +4 -4
- package/dist/hooks/{chunk-WNFJLBXI.js → chunk-QL6QTPWN.js} +3 -3
- package/dist/hooks/{chunk-SL4GQ2XP.js → chunk-XER6SSIB.js} +91 -18
- package/dist/hooks/{chunk-U32VSRNG.js → chunk-YW452ZAX.js} +89 -34
- package/dist/hooks/{chunk-GNDJ7NJ7.js → chunk-YZXYOAZ3.js} +202 -55
- package/dist/hooks/codex-hook-adapter.js +15 -15
- package/dist/hooks/duplicate-read-guard.js +8 -8
- package/dist/hooks/format-edited.js +5 -5
- package/dist/hooks/inputs.json +288 -229
- package/dist/hooks/investigation-guard.js +8 -8
- package/dist/hooks/pretool-guard.js +14 -14
- package/dist/hooks/session-lang.js +69 -16
- package/dist/hooks/stop-guard.js +13 -38
- package/dist/josh.js +62 -9
- package/package.json +4 -3
- package/prompts/review.md +22 -0
- package/scripts/agent/claude-agent-argv.ts +13 -1
- package/scripts/auth/auth-setup-cli.ts +101 -0
- package/scripts/auth/claude-token-output.ts +78 -0
- package/scripts/auth/claude-token.ts +68 -0
- package/scripts/backlog/backlog-drive-epic.ts +24 -8
- package/scripts/backlog/backlog-drive-launch.ts +3 -0
- package/scripts/backlog/backlog-drive-named-offer.ts +7 -2
- package/scripts/backlog/backlog-drive-named.ts +4 -2
- package/scripts/backlog/backlog-drive-unread.ts +56 -0
- package/scripts/backlog/backlog-drive.ts +16 -8
- package/scripts/backlog/backlog-overlap.ts +22 -5
- package/scripts/backlog/backlog-ready.ts +1 -3
- package/scripts/backlog/backlogrun-args.ts +13 -2
- package/scripts/backlog/backlogrun-cli.ts +91 -19
- package/scripts/backlog/backlogrun-launch.ts +24 -0
- package/scripts/backlog/backlogrun-replace.ts +166 -0
- package/scripts/backlog/backlogrun-stop.ts +178 -0
- package/scripts/behavior/behavior-cli.ts +6 -1
- package/scripts/doctor/doctor-io.ts +60 -9
- package/scripts/doctor/doctor.ts +20 -1
- package/scripts/document/document-byte-budget.ts +3 -3
- package/scripts/epic/epic-bundle-cli.ts +8 -8
- package/scripts/epic/epic-bundle-referenced.ts +2 -5
- package/scripts/epic/epic-bundle.ts +39 -4
- package/scripts/exports/unused-members-cli.ts +2 -1
- package/scripts/followup/followup-checks-event.ts +82 -0
- package/scripts/followup/git-followup-finish.ts +16 -2
- package/scripts/followup/git-followup-workflow.ts +5 -2
- package/scripts/followup/git-pr-followup-checks.ts +18 -4
- package/scripts/followup/git-pr-followup-wrapup.ts +5 -0
- package/scripts/followup/git-pr-followup.ts +4 -1
- package/scripts/fresh-machine/fresh-machine-cli.ts +204 -0
- package/scripts/fresh-machine/fresh-machine-docker.ts +65 -0
- package/scripts/fresh-machine/fresh-machine-image.ts +62 -0
- package/scripts/fresh-machine/fresh-machine-log.ts +61 -0
- package/scripts/fresh-machine/fresh-machine-patterns.ts +130 -0
- package/scripts/fresh-machine/fresh-machine-run.ts +215 -0
- package/scripts/fresh-machine/fresh-machine-script.ts +45 -0
- package/scripts/fresh-machine/fresh-machine-spec.ts +57 -0
- package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +34 -0
- package/scripts/fresh-machine/specs/kit-test-backlogrun.json +31 -0
- package/scripts/gate/gate-report.ts +70 -15
- package/scripts/gate/project-checks.ts +13 -2
- package/scripts/gate/scoped-green.ts +2 -1
- package/scripts/gate/verification-gate.ts +4 -3
- package/scripts/gh/gh-author-association.ts +14 -0
- package/scripts/gh/git-gh-conditional.ts +66 -0
- package/scripts/gh/git-gh-exec.ts +1 -1
- package/scripts/gh/git-gh-issue-read.ts +6 -3
- package/scripts/gh/git-gh-issue-write.ts +5 -1
- package/scripts/gh/git-gh-issue.ts +4 -1
- package/scripts/gh/git-pr-checks-eval.ts +13 -0
- package/scripts/gh/git-pr-checks-parse.ts +8 -1
- package/scripts/gh/git-pr-checks-watch.ts +11 -4
- package/scripts/gh/git-pr-checks.ts +36 -2
- package/scripts/git/git-command.ts +16 -4
- package/scripts/git/git-schemas.ts +1 -0
- package/scripts/git/git-spawn-sync.ts +1 -1
- package/scripts/hooks/pretool-guard.ts +3 -1
- package/scripts/init/hook-command-rewrite.ts +31 -0
- package/scripts/init/init-actions.ts +4 -1
- package/scripts/init/init-ai-copy.ts +33 -10
- package/scripts/init/init-basic.ts +8 -3
- package/scripts/init/init-logic-toolchain.ts +76 -0
- package/scripts/init/init-logic.ts +2 -0
- package/scripts/init/init.ts +5 -23
- package/scripts/init/project-profile.ts +36 -7
- package/scripts/init/start-exec.ts +27 -1
- package/scripts/init/start-plan.ts +15 -0
- package/scripts/init/start-steps.ts +46 -0
- package/scripts/init/start.ts +1 -0
- package/scripts/init/workflow-mark.ts +35 -0
- package/scripts/issue/issue-ack-cli.ts +84 -0
- package/scripts/issue/issue-labels.ts +40 -8
- package/scripts/issue/issue-state-cli.ts +14 -4
- package/scripts/issue/issue-watch-diff.ts +152 -0
- package/scripts/issue/issue-watch-gate.ts +81 -0
- package/scripts/issue/issue-watch-hook.ts +54 -0
- package/scripts/issue/issue-watch.ts +240 -0
- package/scripts/issue/measure-rerun-cli.ts +3 -4
- package/scripts/josh/josh-command-map.ts +4 -0
- package/scripts/josh/josh-commands-backlog.ts +2 -2
- package/scripts/josh/josh-commands-development.ts +1 -1
- package/scripts/josh/josh-commands-issue.ts +7 -0
- package/scripts/josh/josh-commands-lane.ts +11 -2
- package/scripts/josh/josh-commands-project.ts +24 -0
- package/scripts/josh/josh-verdict.ts +33 -4
- package/scripts/lane/claude-lane-broker.ts +142 -0
- package/scripts/lane/lane-await.ts +6 -2
- package/scripts/lane/lane-cache.ts +37 -7
- package/scripts/lane/lane-capacity.ts +8 -6
- package/scripts/lane/lane-child-invocation.ts +1 -0
- package/scripts/lane/lane-cli.ts +15 -1
- package/scripts/lane/lane-close.ts +15 -6
- package/scripts/lane/lane-dispatch.ts +36 -10
- package/scripts/lane/lane-environment.ts +1 -1
- package/scripts/lane/lane-launch-cli.ts +13 -4
- package/scripts/lane/lane-ledger.ts +21 -3
- package/scripts/lane/lane-measure-cli.ts +10 -2
- package/scripts/lane/lane-open.ts +9 -1
- package/scripts/lane/lane-preserve.ts +49 -0
- package/scripts/lane/lane-reap.ts +20 -5
- package/scripts/lane/lane-registry.ts +6 -0
- package/scripts/lane/lane-restart-cli.ts +51 -0
- package/scripts/lane/lane-restart.ts +175 -0
- package/scripts/lane/{openai-review-broker.ts → lane-session-broker.ts} +106 -28
- package/scripts/lane/lane-spent.ts +195 -0
- package/scripts/lane/lane-stage-stats.ts +27 -3
- package/scripts/lane/lane-stop-stats.ts +96 -0
- package/scripts/lane/openai-lane-supervisor.ts +2 -2
- package/scripts/lane/process-tools.ts +31 -0
- package/scripts/lib/buffered-process.ts +36 -2
- package/scripts/lib/pnpm-reach.ts +27 -0
- package/scripts/lib/status-icons.ts +5 -1
- package/scripts/lint/lint-parallel.ts +19 -2
- package/scripts/observations/observation-ledger-home.ts +1 -0
- package/scripts/review/review-attest-cli.ts +21 -6
- package/scripts/review/review-brief-cli.ts +2 -2
- package/scripts/review/review-comment-round.ts +114 -0
- package/scripts/review/review-record-cli.ts +39 -17
- package/scripts/rules/implementation-cut.ts +3 -3
- package/scripts/rules/pre-gate-cut.ts +1 -1
- package/scripts/run/board/run-board-checks.ts +42 -0
- package/scripts/run/board/run-board-cli.ts +2 -0
- package/scripts/run/board/run-board-every.ts +3 -1
- package/scripts/run/board/run-board-fit.ts +16 -5
- package/scripts/run/board/run-board-fresh.ts +64 -0
- package/scripts/run/board/run-board-header.ts +129 -53
- package/scripts/run/board/run-board-labels.ts +47 -4
- package/scripts/run/board/run-board-lane-event.ts +66 -0
- package/scripts/run/board/run-board-layout.ts +57 -9
- package/scripts/run/board/run-board-now.ts +234 -0
- package/scripts/run/board/run-board-operation-read.ts +75 -0
- package/scripts/run/board/run-board-operation.ts +150 -0
- package/scripts/run/board/run-board-phase.ts +26 -1
- package/scripts/run/board/run-board-plan.ts +4 -6
- package/scripts/run/board/run-board-read.ts +49 -3
- package/scripts/run/board/run-board-reason.ts +124 -0
- package/scripts/run/board/run-board-render-checks.ts +55 -0
- package/scripts/run/board/run-board-render-legend.ts +12 -3
- package/scripts/run/board/run-board-render-slot.ts +2 -2
- package/scripts/run/board/run-board-render-tail.ts +128 -0
- package/scripts/run/board/run-board-render-thanks.ts +50 -0
- package/scripts/run/board/run-board-render-title.ts +58 -0
- package/scripts/run/board/run-board-render.ts +96 -80
- package/scripts/run/board/run-board-row-yield.ts +32 -0
- package/scripts/run/board/run-board-setup.ts +61 -13
- package/scripts/run/board/run-board-state.ts +10 -0
- package/scripts/run/board/run-board-status.ts +66 -7
- package/scripts/run/board/run-board-thanks-csv.ts +71 -0
- package/scripts/run/board/run-board-thanks-read.ts +112 -0
- package/scripts/run/board/run-board-thanks.ts +53 -0
- package/scripts/run/board/run-board-tick.ts +50 -21
- package/scripts/run/board/run-board-usual.ts +44 -0
- package/scripts/run/carry/run-carry-args.ts +9 -1
- package/scripts/run/carry/run-carry-cli.ts +2 -45
- package/scripts/run/carry/run-carry-end.ts +73 -0
- package/scripts/run/carry/run-carry.ts +8 -1
- package/scripts/run/entry/run-entry-cli.ts +5 -0
- package/scripts/run/event/run-event-checks.ts +54 -0
- package/scripts/run/event/run-event-render.ts +2 -0
- package/scripts/run/event/run-event-scope.ts +1 -0
- package/scripts/run/event/run-event-setup.ts +55 -11
- package/scripts/run/event/run-event-stream.ts +27 -11
- package/scripts/run/merge/run-merge-cli.ts +7 -4
- package/scripts/run/merge/run-merge-ship.ts +61 -0
- package/scripts/run/merge/run-merge-steps.ts +7 -14
- package/scripts/run/merge/run-merge-token.ts +16 -2
- package/scripts/run/run-headless.ts +1 -0
- package/scripts/run/run-step.ts +2 -0
- package/scripts/run/ship/run-ship-cli.ts +22 -2
- package/scripts/run/ship/run-ship-return.ts +31 -13
- package/scripts/run/ship/run-ship-review-steps.ts +17 -4
- package/scripts/run/ship/run-ship-stage.ts +1 -0
- package/scripts/run/wake/run-wake-await.ts +130 -0
- package/scripts/run/wake/run-wake-cli.ts +60 -71
- package/scripts/run/wake/run-wake-describe.ts +34 -2
- package/scripts/run/wake/run-wake-driver.ts +11 -4
- package/scripts/run/wake/run-wake-session.ts +20 -3
- package/scripts/run/wake/run-wake-stop.ts +37 -0
- package/scripts/run/wake/run-wake-supervise.ts +44 -0
- package/scripts/sync/claude-settings-merge.ts +144 -0
- package/scripts/sync/sync-ai-files.ts +28 -3
- package/scripts/sync/sync.ts +15 -0
- package/scripts/test/project-test-script.ts +73 -0
- package/scripts/test/skip-marker.ts +14 -1
- package/scripts/test/test-related.ts +3 -2
- package/scripts/test/test-unit-guard.ts +78 -14
- package/scripts/time-runtime/time-reported-failure.ts +11 -3
- package/scripts/uninstall/distributed-files.ts +92 -0
- package/scripts/uninstall/uninstall-plan.ts +119 -0
- package/scripts/uninstall/uninstall-report.ts +61 -0
- package/scripts/uninstall/uninstall.ts +206 -0
- package/templates/AGENTS.basic.md +4 -0
- package/templates/GEMINI.basic.md +6 -0
- package/dist/hooks/chunk-6V5OPIUU.js +0 -13
- package/scripts/run/board/run-board-track.ts +0 -22
|
@@ -7,6 +7,7 @@ import { init_logic_json_merge } from './init-logic-json-merge'
|
|
|
7
7
|
import { init_logic_secretlint } from './init-logic-secretlint'
|
|
8
8
|
import { init_logic_sonar } from './init-logic-sonar'
|
|
9
9
|
import { init_logic_templates } from './init-logic-templates'
|
|
10
|
+
import { init_logic_toolchain } from './init-logic-toolchain'
|
|
10
11
|
import { init_logic_workspace } from './init-logic-workspace'
|
|
11
12
|
import { init_logic_yaml_merge } from './init-logic-yaml-merge'
|
|
12
13
|
|
|
@@ -417,6 +418,7 @@ const init_logic = {
|
|
|
417
418
|
...init_logic_sonar,
|
|
418
419
|
...init_logic_json_merge,
|
|
419
420
|
...init_logic_secretlint,
|
|
421
|
+
...init_logic_toolchain,
|
|
420
422
|
...init_logic_yaml_merge,
|
|
421
423
|
...init_logic_deploy_vps,
|
|
422
424
|
...distributed_paths,
|
package/scripts/init/init.ts
CHANGED
|
@@ -23,7 +23,6 @@ import { init_file_action } from './init-file-action'
|
|
|
23
23
|
import { init_install } from './init-install'
|
|
24
24
|
import { init_logic } from './init-logic'
|
|
25
25
|
import { PACKAGE_DIR, PROJECT_ROOT } from './init-paths'
|
|
26
|
-
import { kit_development_versions } from './kit-development-versions'
|
|
27
26
|
import { kit_setup_state } from './kit-setup-state'
|
|
28
27
|
import { package_manager_pin } from './package-manager-pin'
|
|
29
28
|
import { project_profile, type ProjectProfile, type ProjectShape } from './project-profile'
|
|
@@ -71,27 +70,8 @@ function get_kit_self_dependency(): Record<string, string> {
|
|
|
71
70
|
return { [KIT_PACKAGE_NAME]: version }
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
// Each config `init` generates runs a CLI or imports types the consumer has to resolve itself
|
|
75
|
-
//
|
|
76
|
-
// Prettier, its preset plugins and @playwright/test come in as peers. Without them the first
|
|
77
|
-
// `josh gate` after `pnpm install` fails.
|
|
78
|
-
const TOOL_DEVELOPMENT_DEPENDENCIES = ['cspell', '@types/node']
|
|
79
|
-
const LEFTHOOK_DEVELOPMENT_DEPENDENCY = 'lefthook'
|
|
80
|
-
|
|
81
|
-
function tool_dependency_names(has_git: boolean): ReadonlyArray<string> {
|
|
82
|
-
return has_git
|
|
83
|
-
? [...TOOL_DEVELOPMENT_DEPENDENCIES, LEFTHOOK_DEVELOPMENT_DEPENDENCY]
|
|
84
|
-
: TOOL_DEVELOPMENT_DEPENDENCIES
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Versions mirror kit's own devDependencies, so the consumer runs the toolchain kit is verified with.
|
|
88
|
-
function get_toolchain_development_dependencies(has_git: boolean): Record<string, string> {
|
|
89
|
-
return kit_development_versions.versions_of([
|
|
90
|
-
...kit_development_versions.peer_names(),
|
|
91
|
-
...tool_dependency_names(has_git),
|
|
92
|
-
])
|
|
93
|
-
}
|
|
94
|
-
|
|
73
|
+
// Each config `init` generates runs a CLI or imports types the consumer has to resolve itself, so
|
|
74
|
+
// the toolchain is added here: without it the first `josh gate` after `pnpm install` fails.
|
|
95
75
|
function apply_dependency_merges(content: string, has_git = true): string {
|
|
96
76
|
const migrated = init_logic.strip_managed_postinstall(content)
|
|
97
77
|
const suggested = init_logic.get_suggested_scripts_for_content(migrated)
|
|
@@ -103,7 +83,7 @@ function apply_dependency_merges(content: string, has_git = true): string {
|
|
|
103
83
|
)
|
|
104
84
|
const with_toolchain = init_logic.merge_development_dependencies(
|
|
105
85
|
merged,
|
|
106
|
-
get_toolchain_development_dependencies(has_git),
|
|
86
|
+
init_logic.get_toolchain_development_dependencies(has_git),
|
|
107
87
|
)
|
|
108
88
|
const with_secretlint = init_logic.merge_secretlint_development_deps(with_toolchain)
|
|
109
89
|
|
|
@@ -147,6 +127,7 @@ function merged_manifest(existing: string, shape: ProjectShape): string {
|
|
|
147
127
|
return init_basic.with_recorded_profile(
|
|
148
128
|
apply_package_json_merges(existing, shape.has_git),
|
|
149
129
|
shape.profile,
|
|
130
|
+
shape.has_workflow,
|
|
150
131
|
)
|
|
151
132
|
}
|
|
152
133
|
|
|
@@ -325,6 +306,7 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
|
325
306
|
const init = {
|
|
326
307
|
copy_ai_file: init_ai_copy.copy_ai_file,
|
|
327
308
|
apply_package_json_merges,
|
|
309
|
+
merged_manifest,
|
|
328
310
|
install_lefthook,
|
|
329
311
|
resolve_kit_package_manager,
|
|
330
312
|
}
|
|
@@ -4,6 +4,8 @@ import { repo_origin } from '#scripts/discovery/repo-origin'
|
|
|
4
4
|
import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
|
|
5
5
|
import { json_value } from '#scripts/lib/json-value'
|
|
6
6
|
import { KIT_PACKAGE_NAME } from '#scripts/version/kit-descriptor'
|
|
7
|
+
import { kit_written_paths } from './kit-written-paths'
|
|
8
|
+
import { workflow_mark } from './workflow-mark'
|
|
7
9
|
|
|
8
10
|
type ProjectProfile = 'basic' | 'full'
|
|
9
11
|
|
|
@@ -17,6 +19,8 @@ interface ProjectShape extends ProfileResult {
|
|
|
17
19
|
has_typescript: boolean
|
|
18
20
|
has_git: boolean
|
|
19
21
|
has_github: boolean
|
|
22
|
+
// Set up by `josh start` for the GitHub Issue workflow (`workflow-mark.ts`).
|
|
23
|
+
has_workflow: boolean
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
const IGNORED_DIRECTORIES = new Set(['.git', 'node_modules', 'dist', 'build', '.svelte-kit'])
|
|
@@ -136,18 +140,27 @@ function scan_directory(directory: string, pending: Array<string>): Array<string
|
|
|
136
140
|
.map((entry) => path.join(directory, entry.name)),
|
|
137
141
|
)
|
|
138
142
|
|
|
139
|
-
return entries.filter((entry) => entry.isFile()).map((entry) => path.
|
|
143
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => path.join(directory, entry.name))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// A file kit wrote says nothing about the project: counted, the `.pnpmfile.mjs` of the first
|
|
147
|
+
// `josh init` would read as the user's Web file on the second and bring Prettier in
|
|
148
|
+
// (joshuafolkken/kit#3713).
|
|
149
|
+
function is_project_file(root: string, file: string): boolean {
|
|
150
|
+
const relative_path = path.relative(root, file).split(path.sep).join(path.posix.sep)
|
|
151
|
+
|
|
152
|
+
return !kit_written_paths.is_kit_written(relative_path)
|
|
140
153
|
}
|
|
141
154
|
|
|
142
155
|
function file_extensions(root: string): Array<string> {
|
|
143
156
|
const pending = [root]
|
|
144
|
-
const
|
|
157
|
+
const files: Array<string> = []
|
|
145
158
|
|
|
146
159
|
for (const directory of pending) {
|
|
147
|
-
|
|
160
|
+
files.push(...scan_directory(directory, pending))
|
|
148
161
|
}
|
|
149
162
|
|
|
150
|
-
return
|
|
163
|
+
return files.filter((file) => is_project_file(root, file)).map((file) => path.extname(file))
|
|
151
164
|
}
|
|
152
165
|
|
|
153
166
|
function scan_files(root: string): { has_web: boolean; has_typescript: boolean } {
|
|
@@ -164,17 +177,33 @@ function has_github_remote(root: string): boolean {
|
|
|
164
177
|
return url !== undefined && repo_origin.parse_origin_url(url) !== undefined
|
|
165
178
|
}
|
|
166
179
|
|
|
180
|
+
function has_git(root: string): boolean {
|
|
181
|
+
return existsSync(path.join(root, '.git'))
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// The run is asked first, so a `josh start` whose setup has not written `package.json` yet reads it.
|
|
185
|
+
function has_workflow(root: string): boolean {
|
|
186
|
+
return workflow_mark.is_requested() || workflow_mark.is_recorded(read_manifest(root))
|
|
187
|
+
}
|
|
188
|
+
|
|
167
189
|
function inspect_project(root: string, requested?: ProjectProfile): ProjectShape {
|
|
168
190
|
const profile = resolve_profile(root, requested)
|
|
169
191
|
const files = scan_files(root)
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
return {
|
|
192
|
+
const is_git = has_git(root)
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
...profile,
|
|
196
|
+
...files,
|
|
197
|
+
has_git: is_git,
|
|
198
|
+
has_github: is_git && has_github_remote(root),
|
|
199
|
+
has_workflow: has_workflow(root),
|
|
200
|
+
}
|
|
173
201
|
}
|
|
174
202
|
|
|
175
203
|
const project_profile = {
|
|
176
204
|
parse_profile,
|
|
177
205
|
resolve_profile,
|
|
206
|
+
has_git,
|
|
178
207
|
inspect_project,
|
|
179
208
|
requested_profile,
|
|
180
209
|
with_legacy_profile_names,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { git_gh_exec } from '#scripts/gh/git-gh-exec'
|
|
2
2
|
import { git_location_environment } from '#scripts/git/git-location-environment'
|
|
3
|
-
import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
|
|
3
|
+
import { git_spawn_sync, type GitSyncOptions } from '#scripts/git/git-spawn-sync'
|
|
4
4
|
import { COMMAND_TIMEOUT_MS, SUITE_TIMEOUT_MS } from '#scripts/lib/timeouts'
|
|
5
5
|
import { execaSync } from 'execa'
|
|
6
6
|
import type { Visibility } from './start-plan'
|
|
@@ -16,6 +16,8 @@ import type { Visibility } from './start-plan'
|
|
|
16
16
|
// project's own commit and push hooks, which may run its whole suite, so it takes the suite budget:
|
|
17
17
|
// long enough for that, short enough that a push that never answers ends the run.
|
|
18
18
|
const SUCCESS_EXIT_CODE = 0
|
|
19
|
+
const GIT_DIRECTORY = '.git'
|
|
20
|
+
const IDENTITY_VARIABLES = ['GIT_AUTHOR_IDENT', 'GIT_COMMITTER_IDENT']
|
|
19
21
|
|
|
20
22
|
function root_environment(): Record<string, string | undefined> {
|
|
21
23
|
return git_location_environment.location_free_environment()
|
|
@@ -29,6 +31,29 @@ function git_read(args: ReadonlyArray<string>, root: string): string | undefined
|
|
|
29
31
|
return git_spawn_sync.read(args, { cwd: root, env: root_environment() })
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
// Asked of git itself, so the answer is the one its commit would reach: config, the environment and
|
|
35
|
+
// the identity git can detect all count (#3754).
|
|
36
|
+
//
|
|
37
|
+
// **Only an answer from git counts as a missing identity.** A git that could not be started or never
|
|
38
|
+
// answered is a different failure, which the first step that runs git reports as what it is.
|
|
39
|
+
function is_identity_refused(variable: string, options: GitSyncOptions): boolean {
|
|
40
|
+
const { exit_code } = git_spawn_sync.run(['var', variable], options)
|
|
41
|
+
|
|
42
|
+
return exit_code !== undefined && exit_code !== SUCCESS_EXIT_CODE
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// A plan that creates the repository asks from where it will be: an identity given per directory
|
|
46
|
+
// (`includeIf "gitdir:…"`) is matched against the git directory, which no `cwd` supplies before
|
|
47
|
+
// `git init` has run. The path stays relative to `root` so git resolves it as its commit will — by
|
|
48
|
+
// the path the user reached the directory through as well as the physical one, which is what a
|
|
49
|
+
// `gitdir:` under a symlinked parent is written against.
|
|
50
|
+
function lacks_git_identity(root: string, is_new_repository: boolean): boolean {
|
|
51
|
+
const location = is_new_repository ? { GIT_DIR: GIT_DIRECTORY } : {}
|
|
52
|
+
const options = { cwd: root, env: { ...root_environment(), ...location } }
|
|
53
|
+
|
|
54
|
+
return IDENTITY_VARIABLES.some((variable) => is_identity_refused(variable, options))
|
|
55
|
+
}
|
|
56
|
+
|
|
32
57
|
function git_run(args: ReadonlyArray<string>, root: string): void {
|
|
33
58
|
const options = { cwd: root, env: root_environment(), timeout: SUITE_TIMEOUT_MS }
|
|
34
59
|
const { exit_code } = git_spawn_sync.run(args, { ...options, stdio: 'inherit' })
|
|
@@ -86,6 +111,7 @@ const start_exec = {
|
|
|
86
111
|
git_succeeds,
|
|
87
112
|
git_read,
|
|
88
113
|
git_run,
|
|
114
|
+
lacks_git_identity,
|
|
89
115
|
is_gh_installed,
|
|
90
116
|
is_gh_signed_in,
|
|
91
117
|
create_github_repository,
|
|
@@ -58,6 +58,9 @@ const STEP_LABELS: Readonly<Record<StartStep, string>> = {
|
|
|
58
58
|
// so they are also the steps a caller's push preflight guards (#3702).
|
|
59
59
|
const GITHUB_WRITE_STEPS: ReadonlySet<StartStep> = new Set(['repository', 'setup_pr'])
|
|
60
60
|
|
|
61
|
+
// The steps that create a commit, which git refuses without an author identity (#3754).
|
|
62
|
+
const COMMIT_STEPS: ReadonlySet<StartStep> = new Set(['commit', 'setup_pr'])
|
|
63
|
+
|
|
61
64
|
function refused(refusal: string): StepPlan {
|
|
62
65
|
return { steps: [], refusal }
|
|
63
66
|
}
|
|
@@ -113,6 +116,10 @@ function has_push(steps: ReadonlyArray<StartStep>): boolean {
|
|
|
113
116
|
return steps.some((step) => GITHUB_WRITE_STEPS.has(step))
|
|
114
117
|
}
|
|
115
118
|
|
|
119
|
+
function has_commit(steps: ReadonlyArray<StartStep>): boolean {
|
|
120
|
+
return steps.some((step) => COMMIT_STEPS.has(step))
|
|
121
|
+
}
|
|
122
|
+
|
|
116
123
|
function interaction_of(options: StartOptions, is_tty: boolean): Verdict<Interaction> {
|
|
117
124
|
if (options.is_yes) return { value: 'unattended', refusal: undefined }
|
|
118
125
|
if (is_tty) return { value: 'interactive', refusal: undefined }
|
|
@@ -167,6 +174,12 @@ function preflight_report(command: string, cause: string): string {
|
|
|
167
174
|
return `josh start stopped before its first step: the push preflight (${command}) failed. Nothing was changed.\nCause: ${cause}`
|
|
168
175
|
}
|
|
169
176
|
|
|
177
|
+
// What a run prints when the scanner the pre-push audit needs could not be put in place: every push
|
|
178
|
+
// the plan makes would be refused by that hook, so it stops while nothing has changed (#3755).
|
|
179
|
+
function scanner_report(cause: string): string {
|
|
180
|
+
return `josh start stopped before its first step: osv-scanner, which the pre-push audit needs, is not installed and could not be fetched. Nothing was changed.\nCause: ${cause}\nInstall osv-scanner (https://google.github.io/osv-scanner/) or restore the network, then run josh start again.`
|
|
181
|
+
}
|
|
182
|
+
|
|
170
183
|
function plan_summary(
|
|
171
184
|
steps: ReadonlyArray<StartStep>,
|
|
172
185
|
labels: Readonly<Record<StartStep, string>> = STEP_LABELS,
|
|
@@ -179,10 +192,12 @@ function plan_summary(
|
|
|
179
192
|
const start_plan = {
|
|
180
193
|
plan_steps,
|
|
181
194
|
has_push,
|
|
195
|
+
has_commit,
|
|
182
196
|
interaction_of,
|
|
183
197
|
github_consent,
|
|
184
198
|
progress_report,
|
|
185
199
|
preflight_report,
|
|
200
|
+
scanner_report,
|
|
186
201
|
plan_summary,
|
|
187
202
|
step_labels,
|
|
188
203
|
STEP_LABELS,
|
|
@@ -2,6 +2,8 @@ import path from 'node:path'
|
|
|
2
2
|
import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
|
|
3
3
|
import { error_text } from '#scripts/lib/error-message'
|
|
4
4
|
import { repository_labels } from '#scripts/repo/repository-labels'
|
|
5
|
+
import { security_audit } from '#scripts/security/security-audit'
|
|
6
|
+
import { security_audit_provision } from '#scripts/security/security-audit-provision'
|
|
5
7
|
import { self_sync_guard } from '#scripts/self-sync-guard/self-sync-guard-logic'
|
|
6
8
|
import { parseCommandString } from 'execa'
|
|
7
9
|
import { main as init_main } from './init'
|
|
@@ -12,6 +14,7 @@ import type { ProjectProfile, ProjectShape } from './project-profile'
|
|
|
12
14
|
import { start_exec } from './start-exec'
|
|
13
15
|
import { start_plan, type GitState, type StartStep, type Visibility } from './start-plan'
|
|
14
16
|
import { start_setup_pr } from './start-setup-pr'
|
|
17
|
+
import { workflow_mark } from './workflow-mark'
|
|
15
18
|
|
|
16
19
|
interface StepContext {
|
|
17
20
|
root: string
|
|
@@ -29,6 +32,12 @@ const GH_INSTALL_HINT =
|
|
|
29
32
|
'GitHub CLI (gh) is not installed. Install it from https://cli.github.com/, run gh auth login, then run josh start again. Nothing was changed.'
|
|
30
33
|
const GH_AUTH_HINT =
|
|
31
34
|
'GitHub CLI is not signed in. Run gh auth login, then run josh start again. Nothing was changed.'
|
|
35
|
+
const GIT_IDENTITY_HINT =
|
|
36
|
+
'Git has no author identity, which the commit josh start creates needs. Run git config --global user.name "Your Name" and git config --global user.email "you@example.com", then run josh start again. Nothing was changed.'
|
|
37
|
+
// Printed before the scanner is looked for: a first run downloads tens of megabytes, and a terminal
|
|
38
|
+
// that says nothing for minutes reads as a hang the user interrupts (#3755).
|
|
39
|
+
const SCANNER_NOTICE =
|
|
40
|
+
'Checking for osv-scanner, which the pre-push audit needs. A first run downloads it, which can take a few minutes on a slow connection.'
|
|
32
41
|
|
|
33
42
|
const { git_succeeds, git_read, run_local } = start_exec
|
|
34
43
|
|
|
@@ -63,6 +72,17 @@ function github_cli_refusal(root: string): string | undefined {
|
|
|
63
72
|
return undefined
|
|
64
73
|
}
|
|
65
74
|
|
|
75
|
+
// A machine Git was just installed on has no author identity, and the commit that needs one comes
|
|
76
|
+
// after the repository and kit's files are written. It is asked for before the first write, and only
|
|
77
|
+
// when the plan commits — a re-run with nothing left to commit is not stopped by it (#3754).
|
|
78
|
+
function git_identity_refusal(steps: ReadonlyArray<StartStep>, root: string): string | undefined {
|
|
79
|
+
if (!start_plan.has_commit(steps)) return undefined
|
|
80
|
+
|
|
81
|
+
return start_exec.lacks_git_identity(root, steps.includes('git_init'))
|
|
82
|
+
? GIT_IDENTITY_HINT
|
|
83
|
+
: undefined
|
|
84
|
+
}
|
|
85
|
+
|
|
66
86
|
// `josh init` refuses to run inside the kit source repository by printing and setting the exit code
|
|
67
87
|
// rather than throwing, so its step would pass and the later steps would commit and publish the
|
|
68
88
|
// source repository. The same check is raised before the first write instead.
|
|
@@ -85,7 +105,10 @@ function create_git_repository(context: StepContext): void {
|
|
|
85
105
|
// A toolkit that layers its files over kit's (`josh-app init`) names its own setup instead, so every
|
|
86
106
|
// other step and guard stays here rather than in a copy of this command (#2872). That command runs
|
|
87
107
|
// without a shell, is given the same profile, and a non-zero exit stops the run before the commit.
|
|
108
|
+
// Either one is told the project is set up for the GitHub Issue workflow (`workflow-mark.ts`).
|
|
88
109
|
function initialize(context: StepContext): void {
|
|
110
|
+
workflow_mark.request()
|
|
111
|
+
|
|
89
112
|
if (context.init_command === undefined) {
|
|
90
113
|
init_main(['--profile', context.profile])
|
|
91
114
|
|
|
@@ -148,6 +171,27 @@ function run_push_preflight(steps: ReadonlyArray<StartStep>, context: StepContex
|
|
|
148
171
|
}
|
|
149
172
|
}
|
|
150
173
|
|
|
174
|
+
// The `full` profile installs kit's pre-push hook, whose audit refuses a push without osv-scanner. The
|
|
175
|
+
// scanner is otherwise fetched at an agent's session start, which a first `josh start` from a terminal
|
|
176
|
+
// has not had, so it is fetched here — forced, because a person asked for this run. A scanner that
|
|
177
|
+
// still cannot be found stops the run before the first step rather than at the push (#3755).
|
|
178
|
+
async function ensure_audit_scanner(
|
|
179
|
+
steps: ReadonlyArray<StartStep>,
|
|
180
|
+
context: StepContext,
|
|
181
|
+
): Promise<void> {
|
|
182
|
+
if (context.profile !== 'full' || !start_plan.has_push(steps)) return
|
|
183
|
+
|
|
184
|
+
const { platform, arch } = process
|
|
185
|
+
|
|
186
|
+
console.info(`\n${SCANNER_NOTICE}`)
|
|
187
|
+
const outcome = await security_audit_provision.report(context.root, platform, arch, true)
|
|
188
|
+
|
|
189
|
+
console.info(outcome)
|
|
190
|
+
if (security_audit.resolve_scanner(context.root, platform) !== undefined) return
|
|
191
|
+
|
|
192
|
+
throw new Error(start_plan.scanner_report(outcome))
|
|
193
|
+
}
|
|
194
|
+
|
|
151
195
|
type StepAction = (context: RunContext) => void | Promise<void>
|
|
152
196
|
|
|
153
197
|
const STEP_ACTIONS: Readonly<Record<StartStep, StepAction>> = {
|
|
@@ -178,6 +222,7 @@ async function run_steps(steps: ReadonlyArray<StartStep>, context: StepContext):
|
|
|
178
222
|
const labels = start_plan.step_labels(context.init_command)
|
|
179
223
|
|
|
180
224
|
run_push_preflight(steps, context)
|
|
225
|
+
await ensure_audit_scanner(steps, context)
|
|
181
226
|
const run_context: RunContext = { ...context, baseline: baseline_of(steps, context) }
|
|
182
227
|
|
|
183
228
|
for (const [index, step] of steps.entries()) {
|
|
@@ -192,6 +237,7 @@ async function run_steps(steps: ReadonlyArray<StartStep>, context: StepContext):
|
|
|
192
237
|
const start_steps = {
|
|
193
238
|
read_git_state,
|
|
194
239
|
github_cli_refusal,
|
|
240
|
+
git_identity_refusal,
|
|
195
241
|
self_run_refusal,
|
|
196
242
|
run_steps,
|
|
197
243
|
}
|
package/scripts/init/start.ts
CHANGED
|
@@ -106,6 +106,7 @@ function prepare(args: ReadonlyArray<string>, is_tty: boolean, root: string): Pr
|
|
|
106
106
|
const consent: Consent = settle(start_plan.github_consent(plan.steps, options, interaction))
|
|
107
107
|
|
|
108
108
|
stop_on(start_steps.github_cli_refusal(root))
|
|
109
|
+
stop_on(start_steps.git_identity_refusal(plan.steps, root))
|
|
109
110
|
|
|
110
111
|
return {
|
|
111
112
|
steps: plan.steps,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { json_value } from '#scripts/lib/json-value'
|
|
2
|
+
|
|
3
|
+
// `josh start` sets a project up for the GitHub Issue workflow and `josh init` alone does not. The
|
|
4
|
+
// difference decides whether a `basic` project receives `.claude/settings.json`, whose permissions,
|
|
5
|
+
// hooks and plugin an unattended `backlogrun` agent runs on (#3796).
|
|
6
|
+
//
|
|
7
|
+
// `josh start` hands the mark to the setup it runs through this variable, not an argument: that setup
|
|
8
|
+
// may be the project's own kit it hands off to or a toolkit's command (`--init-command`), and an older
|
|
9
|
+
// one refuses an argument it does not know but ignores a variable. The setup records the mark in
|
|
10
|
+
// `package.json`, where every later `josh init` and `josh sync` reads it.
|
|
11
|
+
const WORKFLOW_ENV = 'JOSH_WORKFLOW'
|
|
12
|
+
const GITHUB_WORKFLOW = 'github'
|
|
13
|
+
|
|
14
|
+
function is_requested(environment: NodeJS.ProcessEnv = process.env): boolean {
|
|
15
|
+
return environment[WORKFLOW_ENV] === GITHUB_WORKFLOW
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function is_recorded(manifest: Record<string, unknown> | undefined): boolean {
|
|
19
|
+
const josh = manifest?.['josh']
|
|
20
|
+
|
|
21
|
+
return json_value.is_record(josh) && josh['workflow'] === GITHUB_WORKFLOW
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function request(environment: NodeJS.ProcessEnv = process.env): void {
|
|
25
|
+
environment[WORKFLOW_ENV] = GITHUB_WORKFLOW
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The `josh` record's field for the mark — none for a project `josh start` did not set up, so a
|
|
29
|
+
// `josh init` never writes the mark of its own accord.
|
|
30
|
+
function recorded_fields(has_workflow: boolean): Record<string, string> {
|
|
31
|
+
return has_workflow ? { workflow: GITHUB_WORKFLOW } : {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const workflow_mark = { is_requested, is_recorded, request, recorded_fields }
|
|
35
|
+
export { workflow_mark }
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { git_command } from '#scripts/git/git-command'
|
|
4
|
+
import { issue_number_shape } from '#scripts/issue/issue-number-shape'
|
|
5
|
+
import { session_cite } from '#scripts/issue/session-cite'
|
|
6
|
+
import { issue_watch, type Acknowledged } from './issue-watch'
|
|
7
|
+
|
|
8
|
+
// `josh issue:ack <N>` — record that the run has read a person's update to its issue, so the hook,
|
|
9
|
+
// the review attestation and `josh followup` stop raising it. It prints the update it acknowledges:
|
|
10
|
+
// the same read becomes the new baseline (`issue-watch.ts` → `acknowledge`), so nothing that arrived
|
|
11
|
+
// after the hook spoke can be acknowledged without being printed here.
|
|
12
|
+
//
|
|
13
|
+
// **The number is asked for, though the record already carries it**, so an acknowledgement typed for
|
|
14
|
+
// one issue cannot silently clear the watch on another.
|
|
15
|
+
|
|
16
|
+
const SUCCESS_EXIT_CODE = 0
|
|
17
|
+
const FAILURE_EXIT_CODE = 1
|
|
18
|
+
const ARGV_OFFSET = 2
|
|
19
|
+
const USAGE = 'Usage: josh issue:ack <issue-number>'
|
|
20
|
+
const NO_WATCH_LINE =
|
|
21
|
+
'No run recorded a baseline for that issue in this checkout (`josh run:entry` writes it), or the issue could not be read — nothing was acknowledged.'
|
|
22
|
+
const NOTHING_NEW_LINE = 'No person has updated the issue since the run last read it.'
|
|
23
|
+
|
|
24
|
+
function parse_number(argv: ReadonlyArray<string>): string | undefined {
|
|
25
|
+
const [first] = argv
|
|
26
|
+
|
|
27
|
+
if (first === undefined || argv.length !== 1) return undefined
|
|
28
|
+
|
|
29
|
+
return issue_number_shape.ISSUE_NUMBER_PATTERN.test(first) ? first : undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function report(acknowledged: Acknowledged): void {
|
|
33
|
+
const heading = `Acknowledged issue ${session_cite.issue(acknowledged.issue)}.`
|
|
34
|
+
|
|
35
|
+
console.info(
|
|
36
|
+
acknowledged.diff === undefined
|
|
37
|
+
? `${heading} ${NOTHING_NEW_LINE}`
|
|
38
|
+
: `${heading}\n\n${acknowledged.diff}`,
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function acknowledge(issue_number: string): Promise<number> {
|
|
43
|
+
const root = await git_command.repository_root()
|
|
44
|
+
|
|
45
|
+
if (issue_watch.read_record(root)?.issue !== issue_number) {
|
|
46
|
+
console.error(NO_WATCH_LINE)
|
|
47
|
+
|
|
48
|
+
return FAILURE_EXIT_CODE
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const acknowledged = await issue_watch.acknowledge(root)
|
|
52
|
+
|
|
53
|
+
if (acknowledged === undefined) {
|
|
54
|
+
console.error(NO_WATCH_LINE)
|
|
55
|
+
|
|
56
|
+
return FAILURE_EXIT_CODE
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
report(acknowledged)
|
|
60
|
+
|
|
61
|
+
return SUCCESS_EXIT_CODE
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function run(argv: ReadonlyArray<string>): Promise<number> {
|
|
65
|
+
const issue_number = parse_number(argv)
|
|
66
|
+
|
|
67
|
+
if (issue_number === undefined) {
|
|
68
|
+
console.error(USAGE)
|
|
69
|
+
|
|
70
|
+
return FAILURE_EXIT_CODE
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return await acknowledge(issue_number)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function main(argv: ReadonlyArray<string>): Promise<void> {
|
|
77
|
+
process.exitCode = await run(argv)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const issue_ack_cli = { run }
|
|
81
|
+
|
|
82
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) await main(process.argv.slice(ARGV_OFFSET))
|
|
83
|
+
|
|
84
|
+
export { issue_ack_cli }
|
|
@@ -70,6 +70,9 @@ const ALREADY_DONE_LABEL = 'already-done'
|
|
|
70
70
|
// it; **`auto-ok` never rides on it** — `issue-auto-ok.ts` refuses the default for it on every path,
|
|
71
71
|
// since a release is Tier C — and only a person opts it into unattended execution.
|
|
72
72
|
const RELEASE_LABEL = 'release'
|
|
73
|
+
// Applied to an Issue whose second review round was skipped, so the condition stays auditable
|
|
74
|
+
// (`prompts/review.md` → "When round 2 is skipped entirely, and when it is not").
|
|
75
|
+
const REVIEW_ROUND2_SKIPPED_LABEL = 'review-round2-skipped'
|
|
73
76
|
|
|
74
77
|
// The four labels that mean an open issue must not be handed to a run as it stands: an `epic`
|
|
75
78
|
// tracks a batch and is never run directly (its children are), `in-progress` is already claimed by
|
|
@@ -205,8 +208,11 @@ interface LabelDefinition {
|
|
|
205
208
|
}
|
|
206
209
|
|
|
207
210
|
// What `josh start` provisions on a new repository so the first `kickoff` finds every label a run
|
|
208
|
-
// applies with its intended color. The
|
|
209
|
-
//
|
|
211
|
+
// applies with its intended color. The metadata is the one the workflow documents create each label
|
|
212
|
+
// with. **The labels only a person applies are provisioned too**: a person cannot apply a label the
|
|
213
|
+
// repository does not have, so leaving one out made the first `auto-ok` a manual creation step.
|
|
214
|
+
// `bug` is GitHub's default label, kept because `issue:file` applies it to a bug fix and a
|
|
215
|
+
// repository may have been created without the defaults.
|
|
210
216
|
const WORKFLOW_LABELS: ReadonlyArray<LabelDefinition> = [
|
|
211
217
|
{
|
|
212
218
|
name: EPIC_LABEL,
|
|
@@ -235,6 +241,32 @@ const WORKFLOW_LABELS: ReadonlyArray<LabelDefinition> = [
|
|
|
235
241
|
color: 'fef2c0',
|
|
236
242
|
description: 'Tracks the next release; its blockers are the merged work it publishes',
|
|
237
243
|
},
|
|
244
|
+
{
|
|
245
|
+
name: AUTO_OK_LABEL,
|
|
246
|
+
color: '0e8a16',
|
|
247
|
+
description: 'Opted in to unattended execution outside an epic',
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: NEEDS_DECISION_LABEL,
|
|
251
|
+
color: 'd93f0b',
|
|
252
|
+
description: 'Parked: needs a decision before it can run',
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: NEEDS_HUMAN_REVIEW_LABEL,
|
|
256
|
+
color: 'd93f0b',
|
|
257
|
+
description: 'Implement and verify, but stop before committing so a person can look',
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: ALREADY_DONE_LABEL,
|
|
261
|
+
color: '6f42c1',
|
|
262
|
+
description: 'Verified already merged — a person closes it',
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: REVIEW_ROUND2_SKIPPED_LABEL,
|
|
266
|
+
color: 'c5def5',
|
|
267
|
+
description: 'Review round 2 was skipped under the condition in prompts/review.md',
|
|
268
|
+
},
|
|
269
|
+
{ name: BUG_LABEL, color: 'd73a4a', description: "Something isn't working" },
|
|
238
270
|
...FILING_ROUTE_LABELS,
|
|
239
271
|
...DEPTH_LABELS,
|
|
240
272
|
]
|
|
@@ -329,12 +361,6 @@ function depth_label_of(labels: ReadonlyArray<LabelReference> | undefined): stri
|
|
|
329
361
|
// document operates on a label name that does not exist here — a stale or mistyped `labels[]=…`
|
|
330
362
|
// otherwise fails silently at run time. One source, so a label added above joins the scan without a
|
|
331
363
|
// second edit.
|
|
332
|
-
// Applied to an Issue whose second review round was skipped, so the condition stays auditable
|
|
333
|
-
// (`prompts/review.md` → "When round 2 is skipped entirely, and when it is not"). Provisioned inline
|
|
334
|
-
// by the run that applies it rather than through `FILING_ROUTE_LABELS`, but still a managed label the
|
|
335
|
-
// documents operate on, so the label-reference scan has to know it.
|
|
336
|
-
const REVIEW_ROUND2_SKIPPED_LABEL = 'review-round2-skipped'
|
|
337
|
-
|
|
338
364
|
const ALL_LABELS: ReadonlySet<string> = new Set([
|
|
339
365
|
EPIC_LABEL,
|
|
340
366
|
BUG_LABEL,
|
|
@@ -363,6 +389,11 @@ const ALL_LABELS: ReadonlySet<string> = new Set([
|
|
|
363
389
|
IGNORE_FOR_RELEASE_LABEL,
|
|
364
390
|
])
|
|
365
391
|
|
|
392
|
+
// The managed labels `REPOSITORY_LABELS` deliberately does not provision, each with its reason. Every
|
|
393
|
+
// other member of `ALL_LABELS` is provisioned — `issue-labels.test.ts` fails on one that is in
|
|
394
|
+
// neither list, so a label added above cannot silently become one a repository lacks. Empty today.
|
|
395
|
+
const EXEMPT_FROM_PROVISIONING: ReadonlyMap<string, string> = new Map<string, string>()
|
|
396
|
+
|
|
366
397
|
export type { LabelDefinition, ReleaseClassification }
|
|
367
398
|
export {
|
|
368
399
|
ALL_LABELS,
|
|
@@ -379,6 +410,7 @@ export {
|
|
|
379
410
|
DEPTH_LABELS,
|
|
380
411
|
ENHANCEMENT_LABEL,
|
|
381
412
|
EPIC_LABEL,
|
|
413
|
+
EXEMPT_FROM_PROVISIONING,
|
|
382
414
|
FILING_ROUTE_LABELS,
|
|
383
415
|
has_any_label,
|
|
384
416
|
has_label_name,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
4
|
+
import type { IssueReadReason } from '#scripts/gh/git-gh-issue-read'
|
|
4
5
|
import { issue_number_shape } from '#scripts/issue/issue-number-shape'
|
|
5
6
|
import { bounded_pool } from '#scripts/lib/bounded-pool'
|
|
6
7
|
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
7
|
-
import { issue_report_failures
|
|
8
|
+
import { issue_report_failures } from './issue-report-failures'
|
|
8
9
|
import { issue_state, type IssueState } from './issue-state'
|
|
9
10
|
|
|
10
11
|
// `josh issue:state <N> [<N> ...]` — print each issue's state and labels, in the spelling the
|
|
@@ -50,7 +51,14 @@ interface StateRequest {
|
|
|
50
51
|
// One issue's state, or the failure kind. Named for export because `run:prep` reads the state beside
|
|
51
52
|
// the body and the dependency-update scope, reusing this read rather than a
|
|
52
53
|
// second one that would drift from the `human_review` decision `parse_issue_state` owns.
|
|
53
|
-
|
|
54
|
+
//
|
|
55
|
+
// **Why a read failed rides along where the request said it**: `backlog:drive` names the reason when it
|
|
56
|
+
// hands an unreadable named issue back. Both are optional, so a caller that only separates the kinds
|
|
57
|
+
// needs no edit.
|
|
58
|
+
type StateRead =
|
|
59
|
+
| { kind: 'state'; state: IssueState }
|
|
60
|
+
| { kind: 'missing' }
|
|
61
|
+
| { kind: 'unreadable'; reason?: IssueReadReason | undefined; status?: number | undefined }
|
|
54
62
|
|
|
55
63
|
interface IssueReport {
|
|
56
64
|
issue_number: string
|
|
@@ -102,11 +110,13 @@ function parse_request(argv: ReadonlyArray<string>): StateRequest | undefined {
|
|
|
102
110
|
async function read_issue(issue_number: string, repo?: string): Promise<StateRead> {
|
|
103
111
|
const read = await git_gh_command.issue_view_json_classified(issue_number, STATE_FIELDS, repo)
|
|
104
112
|
|
|
105
|
-
if (read.kind !== 'read') return
|
|
113
|
+
if (read.kind !== 'read') return read
|
|
106
114
|
|
|
107
115
|
const parsed = issue_state.parse_issue_state(read.json)
|
|
108
116
|
|
|
109
|
-
return parsed === undefined
|
|
117
|
+
return parsed === undefined
|
|
118
|
+
? { kind: 'unreadable', reason: 'malformed' }
|
|
119
|
+
: { kind: 'state', state: parsed }
|
|
110
120
|
}
|
|
111
121
|
|
|
112
122
|
// Through the shared pool rather than a raw `Promise.all`: each read spawns a `gh` process, and a
|