@joshuafolkken/kit 1.1035.0 → 1.1050.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/verify-ui/SKILL.md +1 -1
- package/.claude/skills/workflow-commands/SKILL.md +2 -2
- package/.claude/skills/workflow-commands/background-commands.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-child.md +2 -2
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-park.md +6 -0
- package/.claude/skills/workflow-commands/backlogrun-progress.md +30 -36
- package/.claude/skills/workflow-commands/backlogrun-steps.md +16 -16
- package/.claude/skills/workflow-commands/chain-rule.md +2 -2
- package/.claude/skills/workflow-commands/followup-reference.md +2 -2
- package/.claude/skills/workflow-commands/issue-comments.md +1 -1
- package/.claude/skills/workflow-commands/observation-filing.md +41 -86
- package/.claude/skills/workflow-commands/pre-gate-cut.md +33 -36
- package/.claude/skills/workflow-commands/working-tree-hold.md +2 -2
- package/.github/workflows/auto-tag.yml +21 -0
- package/.github/workflows/publish.yml +9 -10
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-HU66PWWL.js → chunk-5BK3K7V4.js} +89 -41
- package/dist/hooks/{chunk-GLBDBJOK.js → chunk-7VPDUBRN.js} +49 -151
- package/dist/hooks/{chunk-GJT4BNVE.js → chunk-DMQN45FN.js} +3 -3
- package/dist/hooks/{chunk-D67KVGGY.js → chunk-EJDX2TOB.js} +13 -3
- package/dist/hooks/{chunk-KG6PZD72.js → chunk-GQ7KP45M.js} +2 -2
- package/dist/hooks/{chunk-Y27M6A7X.js → chunk-HJIISEAC.js} +4 -4
- package/dist/hooks/{chunk-XPLP27WA.js → chunk-NAS4C7GP.js} +4 -26
- package/dist/hooks/{chunk-ZXNE6NY3.js → chunk-NJQOBE6L.js} +2 -2
- package/dist/hooks/{chunk-EIFCAJRB.js → chunk-RLDX5SOC.js} +1184 -946
- package/dist/hooks/{chunk-HOYJU5RM.js → chunk-WQ4OSBSS.js} +10 -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 +15 -34
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +23 -16
- package/dist/hooks/stop-guard.js +13 -188
- package/dist/josh.js +4 -106
- package/dist/version/index.js +381 -9
- package/package.json +6 -6
- package/prompts/collaboration-workflow/file-edits.md +6 -53
- package/prompts/collaboration-workflow/residency.md +1 -1
- package/prompts/collaboration-workflow/rule-delivery.md +4 -35
- package/prompts/collaboration-workflow/shell-body.md +7 -9
- package/prompts/collaboration-workflow/turn-batching.md +21 -37
- package/prompts/collaboration-workflow/wip-cap.md +15 -18
- package/prompts/sonar-hotspot-handling.md +1 -1
- package/scripts/adopt/adopt-toolkits.ts +2 -0
- package/scripts/auto-ok/auto-ok-cli.ts +9 -9
- package/scripts/backlog/backlog-next.ts +24 -8
- package/scripts/backlog/backlog-plan-cli.ts +5 -1
- package/scripts/backlog/backlog-pool.ts +31 -11
- package/scripts/backlog/backlog-rank.ts +106 -0
- package/scripts/backlog/backlog-waves.ts +42 -29
- package/scripts/build/build-hooks.ts +7 -2
- package/scripts/delegation/delegation-policy.ts +11 -0
- package/scripts/document/document-byte-budget.ts +5 -5
- package/scripts/document/entry-read-budget.ts +5 -4
- package/scripts/epic/epic-lane-offer.ts +11 -22
- package/scripts/epic/epic-rank.ts +37 -0
- package/scripts/epic/epic-solo.ts +29 -28
- package/scripts/git/git-command.ts +1 -1
- package/scripts/git/git-execa-error.ts +6 -1
- package/scripts/git/git-next-issues.ts +34 -5
- package/scripts/git/git-preflight.ts +22 -4
- package/scripts/git/git-push-transport.ts +12 -69
- package/scripts/git/git-spawn.ts +79 -3
- package/scripts/git/git-ssh-keepalive.ts +74 -0
- package/scripts/git/gone-branch.ts +3 -2
- package/scripts/git/issue-labels.ts +12 -0
- package/scripts/git/issue-rank.ts +89 -0
- package/scripts/hooks/format-edited-cli.ts +15 -0
- package/scripts/hooks/format-edited-file.ts +2 -9
- package/scripts/hooks/pretool-guard-cli.ts +21 -0
- package/scripts/hooks/pretool-guard.ts +14 -15
- package/scripts/init/start-exec.ts +7 -1
- package/scripts/init/start-plan.ts +18 -5
- package/scripts/init/start-setup-pr.ts +46 -19
- package/scripts/init/start-steps.ts +41 -9
- package/scripts/init/start.ts +40 -10
- package/scripts/issue/issue-cite.ts +19 -1
- package/scripts/josh/josh-command-map.ts +0 -102
- package/scripts/josh/josh-commands-development.ts +2 -2
- package/scripts/josh/josh-commands-project.ts +1 -1
- package/scripts/lane/lane-child-marker.ts +16 -2
- package/scripts/lane/lane-cli.ts +32 -14
- package/scripts/lane/lane-report.ts +10 -4
- package/scripts/propagate/propagate-plan.ts +185 -0
- package/scripts/propagate/propagate-run.ts +5 -1
- package/scripts/propagate/propagate-steps.ts +16 -1
- package/scripts/propagate/propagate-targets.ts +13 -2
- package/scripts/propagate/propagate.ts +60 -42
- package/scripts/registry-migration/migrate-logic.ts +1 -0
- package/scripts/release/github-release.ts +1 -1
- package/scripts/review/review-attest.ts +44 -0
- package/scripts/review/review-brief-cli.ts +49 -2
- package/scripts/review/review-brief.ts +46 -1
- package/scripts/review/review-stamps.ts +7 -0
- package/scripts/rules/implementation-cut.ts +1 -1
- package/scripts/rules/issue-fold.ts +1 -2
- package/scripts/rules/lane-carry-conflict.ts +1 -2
- package/scripts/rules/lane-park.ts +1 -2
- package/scripts/rules/pre-gate-cut.ts +2 -2
- package/scripts/run/run-headless.ts +22 -6
- package/scripts/run/run-parent-cut-hook.ts +144 -0
- package/scripts/run/run-progress-read.ts +2 -1
- package/scripts/run/run-progress.ts +21 -8
- package/scripts/run/run-ship-cli.ts +6 -74
- package/scripts/run/run-ship-preflight.ts +87 -0
- package/scripts/run/run-ship-review-steps.ts +5 -26
- package/scripts/run/run-ship-review.ts +9 -0
- package/scripts/run/run-ship-scoped.ts +44 -0
- package/scripts/run/run-ship-stage.ts +5 -1
- package/scripts/run/run-ship-steps.ts +105 -0
- package/scripts/run/run-ship.ts +2 -0
- package/scripts/run/run-wake-cli.ts +2 -2
- package/scripts/run/run-wake-describe.ts +19 -1
- package/scripts/run/run-watcher-hook.ts +1 -1
- package/scripts/time-runtime/time-bundle-call.ts +2 -2
- package/scripts/version/npm-registry.ts +97 -0
- package/scripts/version/version-commands.ts +5 -4
- package/scripts/version/version-remote.ts +26 -12
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
#!/usr/bin/env tsx
|
|
2
|
-
import { text } from 'node:stream/consumers'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
4
1
|
import { duplicate_read_outcome } from '#scripts/delegation/duplicate-read-guard'
|
|
5
2
|
import { investigation_refusal } from '#scripts/delegation/investigation-guard'
|
|
6
|
-
import {
|
|
3
|
+
import type { GuardOutcome } from '#scripts/josh/hook-decision'
|
|
7
4
|
import { delivered_rules } from '#scripts/rules/delivered-rules'
|
|
5
|
+
import { run_parent_cut_hook } from '#scripts/run/run-parent-cut-hook'
|
|
8
6
|
import { run_watcher_hook } from '#scripts/run/run-watcher-hook'
|
|
9
7
|
import { batch_outcome } from './batch-guard'
|
|
10
8
|
|
|
@@ -71,26 +69,27 @@ function pretool_outcome(raw_payload: string): GuardOutcome {
|
|
|
71
69
|
// as `combine_outcomes` orders them, and the watcher's refusal fills a clear verdict rather than
|
|
72
70
|
// overriding one. It fires once per run itself (`run-watcher-hook.ts`), so a stale watcher refuses the
|
|
73
71
|
// first call but not the `pnpm josh run:progress --wait` that fixes it.
|
|
72
|
+
//
|
|
73
|
+
// **The parent hand-off guard is the second asynchronous rule, asked after the watcher's**
|
|
74
|
+
// (joshuafolkken/kit#2947). It reads the carry record to tell the `backlogrun` parent apart, so it sits
|
|
75
|
+
// beside the watcher rather than among the synchronous rows; a stale watcher is the cheaper fix and is
|
|
76
|
+
// surfaced first.
|
|
77
|
+
async function async_reason(raw_payload: string): Promise<string | undefined> {
|
|
78
|
+
const watcher = await run_watcher_hook.watcher_hook_reason(raw_payload)
|
|
79
|
+
|
|
80
|
+
return watcher ?? (await run_parent_cut_hook.parent_cut_reason(raw_payload))
|
|
81
|
+
}
|
|
82
|
+
|
|
74
83
|
async function pretool_outcome_async(raw_payload: string): Promise<GuardOutcome> {
|
|
75
84
|
const base = pretool_outcome(raw_payload)
|
|
76
85
|
|
|
77
86
|
if (!is_clear(base)) return base
|
|
78
87
|
|
|
79
|
-
const reason = await
|
|
88
|
+
const reason = await async_reason(raw_payload)
|
|
80
89
|
|
|
81
90
|
return reason === undefined ? base : { reason, notice: undefined, fault: undefined }
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
const pretool_guard = { combine_outcomes, pretool_outcome, pretool_outcome_async }
|
|
85
94
|
|
|
86
|
-
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
87
|
-
if (process.stdin.isTTY) hook_decision.report_no_payload('pretool:guard')
|
|
88
|
-
else {
|
|
89
|
-
const raw_payload = await text(process.stdin)
|
|
90
|
-
|
|
91
|
-
hook_decision.load_environment_file()
|
|
92
|
-
hook_decision.emit_outcome(await pretool_outcome_async(raw_payload))
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
95
|
export { pretool_guard, pretool_outcome, pretool_outcome_async }
|
|
@@ -23,6 +23,12 @@ function run(command: string, args: ReadonlyArray<string>, root: string): void {
|
|
|
23
23
|
execaSync(command, args, { ...git_gh_exec.direct_environment(), cwd: root, stdio: 'inherit' })
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
// A caller's own initialize command stands in for the in-process `josh init`, so it keeps the
|
|
27
|
+
// environment that setup would have had — its install goes through a scanner's proxy as usual.
|
|
28
|
+
function run_local(command: string, args: ReadonlyArray<string>, root: string): void {
|
|
29
|
+
execaSync(command, args, { cwd: root, stdio: 'inherit' })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const start_exec = { succeeds, read_output, run, run_local }
|
|
27
33
|
|
|
28
34
|
export { start_exec }
|
|
@@ -8,6 +8,7 @@ interface StartOptions {
|
|
|
8
8
|
is_yes: boolean
|
|
9
9
|
is_github: boolean
|
|
10
10
|
visibility: Visibility
|
|
11
|
+
init_command: string | undefined
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
type StartStep = 'git_init' | 'initialize' | 'commit' | 'repository' | 'labels' | 'setup_pr'
|
|
@@ -137,20 +138,31 @@ function github_consent(
|
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
// A run whose initialize step was handed to a caller's command names that command wherever the step
|
|
142
|
+
// is printed, so the plan, the progress and a failure all say what actually runs (#2872).
|
|
143
|
+
function step_labels(init_command: string | undefined): Readonly<Record<StartStep, string>> {
|
|
144
|
+
if (init_command === undefined) return STEP_LABELS
|
|
145
|
+
|
|
146
|
+
return { ...STEP_LABELS, initialize: `Initialize with ${init_command}` }
|
|
147
|
+
}
|
|
148
|
+
|
|
140
149
|
// What a failed run prints, so the user can tell how far it got before deciding how to resume.
|
|
141
150
|
function progress_report(
|
|
142
151
|
failed: StartStep,
|
|
143
152
|
completed: ReadonlyArray<StartStep>,
|
|
144
153
|
cause: string,
|
|
154
|
+
labels: Readonly<Record<StartStep, string>> = STEP_LABELS,
|
|
145
155
|
): string {
|
|
146
|
-
const done =
|
|
147
|
-
completed.length === 0 ? 'nothing' : completed.map((step) => STEP_LABELS[step]).join('; ')
|
|
156
|
+
const done = completed.length === 0 ? 'nothing' : completed.map((step) => labels[step]).join('; ')
|
|
148
157
|
|
|
149
|
-
return `josh start stopped at: ${
|
|
158
|
+
return `josh start stopped at: ${labels[failed]}\nCompleted: ${done}\nCause: ${cause}`
|
|
150
159
|
}
|
|
151
160
|
|
|
152
|
-
function plan_summary(
|
|
153
|
-
|
|
161
|
+
function plan_summary(
|
|
162
|
+
steps: ReadonlyArray<StartStep>,
|
|
163
|
+
labels: Readonly<Record<StartStep, string>> = STEP_LABELS,
|
|
164
|
+
): string {
|
|
165
|
+
const lines = steps.map((step, index) => ` ${String(index + 1)}. ${labels[step]}`)
|
|
154
166
|
|
|
155
167
|
return ['josh start will:', ...lines].join('\n')
|
|
156
168
|
}
|
|
@@ -161,6 +173,7 @@ const start_plan = {
|
|
|
161
173
|
github_consent,
|
|
162
174
|
progress_report,
|
|
163
175
|
plan_summary,
|
|
176
|
+
step_labels,
|
|
164
177
|
STEP_LABELS,
|
|
165
178
|
}
|
|
166
179
|
export { start_plan }
|
|
@@ -12,15 +12,22 @@ import { start_exec } from './start-exec'
|
|
|
12
12
|
const SETUP_TITLE = `Set up ${KIT_PACKAGE_NAME}`
|
|
13
13
|
const SETUP_BODY = [
|
|
14
14
|
`Adds the files \`josh start\` wrote while setting up ${KIT_PACKAGE_NAME}, so main carries the`,
|
|
15
|
-
'rules, settings and workflows every later Issue is run with. Only the files
|
|
16
|
-
'pull request; anything else in the working tree is left as it was.',
|
|
15
|
+
'rules, settings and workflows every later Issue is run with. Only the files the setup wrote are in',
|
|
16
|
+
'the pull request; anything else in the working tree is left as it was.',
|
|
17
17
|
].join('\n')
|
|
18
|
+
const NAME_ONLY = '--name-only'
|
|
18
19
|
const NOTHING_TO_COMMIT = 'The kit setup has no changes to commit, so no pull request was opened.'
|
|
19
20
|
const OPEN_SETUP_ISSUES_QUERY = `?state=open&labels=${IGNORE_FOR_RELEASE_LABEL}&per_page=100`
|
|
20
21
|
const FIRST_SETUP_ISSUE_FILTER = `[.[] | select(.title == "${SETUP_TITLE}") | .number][0] // empty`
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
// Listings are read NUL-separated (`-z`), so a path is never quoted — a name a caller's initialize
|
|
24
|
+
// command wrote need not be ASCII the way kit's own are (#2872).
|
|
25
|
+
function paths_of(output: string | undefined): Array<string> {
|
|
26
|
+
return (output ?? '').split('\0').filter((listed) => listed.length > 0)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function listing(args: ReadonlyArray<string>, root: string): Array<string> {
|
|
30
|
+
return paths_of(start_exec.read_output('git', [...args, '-z'], root))
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
function setup_issue(issue_number: string): IssueInfo {
|
|
@@ -42,17 +49,18 @@ function is_setup_branch(branch: string | undefined): boolean {
|
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
// Untracked files the ignore rules let through, plus tracked files the setup changed — two plain
|
|
45
|
-
// listings, so nothing has to be parsed out of `git status`.
|
|
46
|
-
// quoting of other names never hides one.
|
|
52
|
+
// listings, so nothing has to be parsed out of `git status`.
|
|
47
53
|
function changed_paths(root: string): Array<string> {
|
|
48
|
-
const untracked =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
const untracked = listing(['ls-files', '--others', '--exclude-standard'], root)
|
|
55
|
+
const modified = listing(['diff', NAME_ONLY, 'HEAD'], root)
|
|
56
|
+
|
|
57
|
+
return [...untracked, ...modified]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// On the setup branch a failed commit left, that run's `git add` already staged its files, so they
|
|
61
|
+
// read as changed before this run's command ran — the staged ones count as setup output (#2872).
|
|
62
|
+
function carried_paths(root: string, current: string | undefined): Array<string> {
|
|
63
|
+
return is_setup_branch(current) ? listing(['diff', NAME_ONLY, '--cached'], root) : []
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
// The Issue carries the release classification the pull request is opened with, so the PR step finds
|
|
@@ -113,10 +121,30 @@ function commit_kit_paths(paths: ReadonlyArray<string>, issue: IssueInfo, root:
|
|
|
113
121
|
start_exec.run('git', ['commit', '--message', issue.commit_message, '--', ...paths], root)
|
|
114
122
|
}
|
|
115
123
|
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
// A caller's initialize command writes files kit cannot name, so with a `baseline` — what was already
|
|
125
|
+
// changed before that command ran — everything changed since joins kit's own files, while a change
|
|
126
|
+
// the user had made before the run still stays out (#2872).
|
|
127
|
+
function setup_paths(
|
|
128
|
+
root: string,
|
|
129
|
+
baseline: ReadonlyArray<string> | undefined,
|
|
130
|
+
current: string | undefined,
|
|
131
|
+
): Array<string> {
|
|
132
|
+
const changed = changed_paths(root)
|
|
133
|
+
const kit_paths = kit_written_paths.select(changed)
|
|
134
|
+
|
|
135
|
+
if (baseline === undefined) return kit_paths
|
|
136
|
+
|
|
137
|
+
const carried = carried_paths(root, current)
|
|
138
|
+
const written = changed.filter((path) => !baseline.includes(path) || carried.includes(path))
|
|
139
|
+
|
|
140
|
+
return [...new Set([...kit_paths, ...written])]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Issue → branch → only the setup's files committed → push → pull request. The merge is left to a
|
|
144
|
+
// person: it is the one step that changes main.
|
|
145
|
+
async function open(root: string, baseline?: ReadonlyArray<string>): Promise<void> {
|
|
146
|
+
const current = start_exec.read_output('git', ['symbolic-ref', '--short', 'HEAD'], root)
|
|
147
|
+
const paths = setup_paths(root, baseline, current)
|
|
120
148
|
|
|
121
149
|
if (paths.length === 0) {
|
|
122
150
|
console.info(NOTHING_TO_COMMIT)
|
|
@@ -124,7 +152,6 @@ async function open(root: string): Promise<void> {
|
|
|
124
152
|
return
|
|
125
153
|
}
|
|
126
154
|
|
|
127
|
-
const current = start_exec.read_output('git', ['symbolic-ref', '--short', 'HEAD'], root)
|
|
128
155
|
const issue = resolve_issue(current)
|
|
129
156
|
|
|
130
157
|
switch_to(issue.branch_name, current, root)
|
|
@@ -2,6 +2,7 @@ import path from 'node:path'
|
|
|
2
2
|
import { error_text } from '#scripts/lib/error-message'
|
|
3
3
|
import { repository_labels } from '#scripts/repo/repository-labels'
|
|
4
4
|
import { self_sync_guard } from '#scripts/self-sync-guard/self-sync-guard-logic'
|
|
5
|
+
import { parseCommandString } from 'execa'
|
|
5
6
|
import { main as init_main } from './init'
|
|
6
7
|
import { PACKAGE_DIR } from './init-paths'
|
|
7
8
|
import { kit_setup_state } from './kit-setup-state'
|
|
@@ -14,6 +15,11 @@ interface StepContext {
|
|
|
14
15
|
root: string
|
|
15
16
|
profile: ProjectProfile
|
|
16
17
|
visibility: Visibility
|
|
18
|
+
init_command: string | undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface RunContext extends StepContext {
|
|
22
|
+
baseline: ReadonlyArray<string> | undefined
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
const DEFAULT_BRANCH = 'main'
|
|
@@ -22,7 +28,7 @@ const GH_INSTALL_HINT =
|
|
|
22
28
|
const GH_AUTH_HINT =
|
|
23
29
|
'GitHub CLI is not signed in. Run gh auth login, then run josh start again. Nothing was changed.'
|
|
24
30
|
|
|
25
|
-
const { succeeds, read_output, run } = start_exec
|
|
31
|
+
const { succeeds, read_output, run, run_local } = start_exec
|
|
26
32
|
|
|
27
33
|
function read_git_state(root: string, shape: ProjectShape): GitState {
|
|
28
34
|
const { has_git, has_github } = shape
|
|
@@ -67,8 +73,19 @@ function create_git_repository(context: StepContext): void {
|
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
// The same setup `josh init` runs, called rather than copied, with the profile the user confirmed.
|
|
76
|
+
// A toolkit that layers its files over kit's (`josh-app init`) names its own setup instead, so every
|
|
77
|
+
// other step and guard stays here rather than in a copy of this command (#2872). That command runs
|
|
78
|
+
// without a shell, is given the same profile, and a non-zero exit stops the run before the commit.
|
|
70
79
|
function initialize(context: StepContext): void {
|
|
71
|
-
|
|
80
|
+
if (context.init_command === undefined) {
|
|
81
|
+
init_main(['--profile', context.profile])
|
|
82
|
+
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const [command = '', ...args] = parseCommandString(context.init_command)
|
|
87
|
+
|
|
88
|
+
run_local(command, [...args, '--profile', context.profile], context.root)
|
|
72
89
|
}
|
|
73
90
|
|
|
74
91
|
function commit_all(context: StepContext): void {
|
|
@@ -97,11 +114,22 @@ function add_missing_labels(): void {
|
|
|
97
114
|
|
|
98
115
|
// Main already has a history here, so the setup reaches it the way every other change does: through
|
|
99
116
|
// an Issue and a pull request a person merges (joshuafolkken/kit#2816).
|
|
100
|
-
async function open_setup_pull_request(context:
|
|
101
|
-
await start_setup_pr.open(context.root)
|
|
117
|
+
async function open_setup_pull_request(context: RunContext): Promise<void> {
|
|
118
|
+
await start_setup_pr.open(context.root, context.baseline)
|
|
102
119
|
}
|
|
103
120
|
|
|
104
|
-
|
|
121
|
+
// What was already changed before a caller's initialize command ran, read only when the setup pull
|
|
122
|
+
// request will need to tell that command's files from the user's own (#2872).
|
|
123
|
+
function baseline_of(
|
|
124
|
+
steps: ReadonlyArray<StartStep>,
|
|
125
|
+
context: StepContext,
|
|
126
|
+
): ReadonlyArray<string> | undefined {
|
|
127
|
+
if (context.init_command === undefined || !steps.includes('setup_pr')) return undefined
|
|
128
|
+
|
|
129
|
+
return start_setup_pr.changed_paths(context.root)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
type StepAction = (context: RunContext) => void | Promise<void>
|
|
105
133
|
|
|
106
134
|
const STEP_ACTIONS: Readonly<Record<StartStep, StepAction>> = {
|
|
107
135
|
git_init: create_git_repository,
|
|
@@ -114,24 +142,28 @@ const STEP_ACTIONS: Readonly<Record<StartStep, StepAction>> = {
|
|
|
114
142
|
|
|
115
143
|
async function run_step(
|
|
116
144
|
step: StartStep,
|
|
117
|
-
context:
|
|
145
|
+
context: RunContext,
|
|
118
146
|
completed: ReadonlyArray<StartStep>,
|
|
119
147
|
): Promise<void> {
|
|
120
148
|
try {
|
|
121
149
|
await STEP_ACTIONS[step](context)
|
|
122
150
|
} catch (error) {
|
|
123
151
|
const cause = error_text.message_of(error)
|
|
152
|
+
const labels = start_plan.step_labels(context.init_command)
|
|
124
153
|
|
|
125
|
-
throw new Error(start_plan.progress_report(step, completed, cause), { cause: error })
|
|
154
|
+
throw new Error(start_plan.progress_report(step, completed, cause, labels), { cause: error })
|
|
126
155
|
}
|
|
127
156
|
}
|
|
128
157
|
|
|
129
158
|
async function run_steps(steps: ReadonlyArray<StartStep>, context: StepContext): Promise<void> {
|
|
159
|
+
const labels = start_plan.step_labels(context.init_command)
|
|
160
|
+
const run_context: RunContext = { ...context, baseline: baseline_of(steps, context) }
|
|
161
|
+
|
|
130
162
|
for (const [index, step] of steps.entries()) {
|
|
131
163
|
const position = `${String(index + 1)}/${String(steps.length)}`
|
|
132
164
|
|
|
133
|
-
console.info(`\n[${position}] ${
|
|
134
|
-
await run_step(step,
|
|
165
|
+
console.info(`\n[${position}] ${labels[step]}`)
|
|
166
|
+
await run_step(step, run_context, steps.slice(0, index))
|
|
135
167
|
}
|
|
136
168
|
}
|
|
137
169
|
|
package/scripts/init/start.ts
CHANGED
|
@@ -14,7 +14,9 @@ import { start_steps } from './start-steps'
|
|
|
14
14
|
|
|
15
15
|
const ARGUMENT_START_INDEX = 2
|
|
16
16
|
const READY_MESSAGE = 'GitHub workflow ready. Use kickoff new in your assistant to plan an Issue.'
|
|
17
|
-
const START_USAGE =
|
|
17
|
+
const START_USAGE =
|
|
18
|
+
'josh start [--profile basic|full] [--yes] [--github] [--public] [--init-command <command>]'
|
|
19
|
+
const INIT_COMMAND_FLAG = '--init-command'
|
|
18
20
|
const YES_FLAG = '--yes'
|
|
19
21
|
const GITHUB_FLAG = '--github'
|
|
20
22
|
const PUBLIC_FLAG = '--public'
|
|
@@ -26,16 +28,42 @@ interface Prepared {
|
|
|
26
28
|
choices: Choices
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
interface InitCommandSplit {
|
|
32
|
+
init_command: string | undefined
|
|
33
|
+
rest: ReadonlyArray<string>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// A value that is blank or another flag means the command itself was left out, which would otherwise
|
|
37
|
+
// swallow the next switch or hand the initialize step nothing to run.
|
|
38
|
+
function is_command_value(value: string | undefined): value is string {
|
|
39
|
+
return value !== undefined && value.trim() !== '' && !value.startsWith('-')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// The command arrives as the one argument after its flag and is removed together with it.
|
|
43
|
+
function split_init_command(args: ReadonlyArray<string>): InitCommandSplit {
|
|
44
|
+
const index = args.indexOf(INIT_COMMAND_FLAG)
|
|
45
|
+
|
|
46
|
+
if (index === -1) return { init_command: undefined, rest: args }
|
|
47
|
+
|
|
48
|
+
const value = args[index + 1]
|
|
49
|
+
|
|
50
|
+
if (!is_command_value(value)) throw new Error(`Usage: ${START_USAGE}`)
|
|
51
|
+
|
|
52
|
+
return { init_command: value, rest: args.filter((_, at) => at !== index && at !== index + 1) }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The switches and the initialize command are peeled off first, so what remains is exactly the
|
|
56
|
+
// `--profile` pair `josh init` accepts — one parser for the profile in both commands.
|
|
31
57
|
function parse_start_options(args: ReadonlyArray<string>): StartOptions {
|
|
32
|
-
const
|
|
58
|
+
const { init_command, rest } = split_init_command(args)
|
|
59
|
+
const profile_args = rest.filter((argument) => !SWITCHES.has(argument))
|
|
33
60
|
|
|
34
61
|
return {
|
|
35
62
|
profile: project_profile.requested_profile(profile_args, START_USAGE),
|
|
36
|
-
is_yes:
|
|
37
|
-
is_github:
|
|
38
|
-
visibility:
|
|
63
|
+
is_yes: rest.includes(YES_FLAG),
|
|
64
|
+
is_github: rest.includes(GITHUB_FLAG),
|
|
65
|
+
visibility: rest.includes(PUBLIC_FLAG) ? 'public' : 'private',
|
|
66
|
+
init_command,
|
|
39
67
|
}
|
|
40
68
|
}
|
|
41
69
|
|
|
@@ -83,11 +111,13 @@ function prepare(args: ReadonlyArray<string>, is_tty: boolean, root: string): Pr
|
|
|
83
111
|
async function main(args: ReadonlyArray<string>, is_tty: boolean): Promise<void> {
|
|
84
112
|
const { steps, interaction, choices } = prepare(args, is_tty, process.cwd())
|
|
85
113
|
|
|
86
|
-
console.info(start_plan.plan_summary(steps))
|
|
87
|
-
const profile = await resolve_profile(choices, interaction)
|
|
88
114
|
const { root, options } = choices
|
|
115
|
+
const { visibility, init_command } = options
|
|
116
|
+
|
|
117
|
+
console.info(start_plan.plan_summary(steps, start_plan.step_labels(init_command)))
|
|
118
|
+
const profile = await resolve_profile(choices, interaction)
|
|
89
119
|
|
|
90
|
-
await start_steps.run_steps(steps, { root, profile, visibility
|
|
120
|
+
await start_steps.run_steps(steps, { root, profile, visibility, init_command })
|
|
91
121
|
console.info(`\n${READY_MESSAGE}`)
|
|
92
122
|
}
|
|
93
123
|
|
|
@@ -40,6 +40,23 @@ function citation_line(slug: string, number: string, summary: string): string {
|
|
|
40
40
|
return `[#${number}](${issue_url(slug, number)})${SUMMARY_SEPARATOR}${summary}`
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// How a listing names one issue, given what it already holds (joshuafolkken/kit#2943). A progress
|
|
44
|
+
// command's output is copied into a report verbatim, so the line it prints is the citation the report
|
|
45
|
+
// carries: the full form when the title is in hand, the bare number-link when only the repository is,
|
|
46
|
+
// and the plain `#N` only when the repository could not be read at all.
|
|
47
|
+
type IssueCiter = (number: string) => string
|
|
48
|
+
|
|
49
|
+
function citer(slug: string | undefined, titles: ReadonlyMap<string, string>): IssueCiter {
|
|
50
|
+
return function cite(number: string): string {
|
|
51
|
+
if (slug === undefined) return `#${number}`
|
|
52
|
+
const title = titles.get(number)
|
|
53
|
+
|
|
54
|
+
return title === undefined
|
|
55
|
+
? `[#${number}](${issue_url(slug, number)})`
|
|
56
|
+
: citation_line(slug, number, title)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
43
60
|
function qualified_target(token: string): CiteTarget | undefined {
|
|
44
61
|
const match = REPO_QUALIFIED.exec(token)
|
|
45
62
|
if (match === null) return undefined
|
|
@@ -90,6 +107,7 @@ function no_repo_line(target: CiteTarget): string {
|
|
|
90
107
|
const issue_cite = {
|
|
91
108
|
SUMMARY_SEPARATOR,
|
|
92
109
|
citation_line,
|
|
110
|
+
citer,
|
|
93
111
|
issue_url,
|
|
94
112
|
label,
|
|
95
113
|
missing_line,
|
|
@@ -98,5 +116,5 @@ const issue_cite = {
|
|
|
98
116
|
unreadable_line,
|
|
99
117
|
}
|
|
100
118
|
|
|
101
|
-
export type { CiteTarget }
|
|
119
|
+
export type { CiteTarget, IssueCiter }
|
|
102
120
|
export { issue_cite }
|
|
@@ -38,8 +38,6 @@ const ALIASES: Record<string, string> = {
|
|
|
38
38
|
ln: 'lines',
|
|
39
39
|
by: 'bytes',
|
|
40
40
|
f: 'format',
|
|
41
|
-
fd: 'format:edited',
|
|
42
|
-
bg: 'batch:guard',
|
|
43
41
|
sd: 'cspell:dot',
|
|
44
42
|
bh: 'behavior',
|
|
45
43
|
t: 'test',
|
|
@@ -48,8 +46,6 @@ const ALIASES: Record<string, string> = {
|
|
|
48
46
|
td: 'test:declared',
|
|
49
47
|
trd: 'test:red',
|
|
50
48
|
te: 'test:e2e',
|
|
51
|
-
er: 'e2e:retry-check',
|
|
52
|
-
sl: 'session:lang',
|
|
53
49
|
c: 'check',
|
|
54
50
|
pt: 'port',
|
|
55
51
|
i: 'init',
|
|
@@ -57,19 +53,12 @@ const ALIASES: Record<string, string> = {
|
|
|
57
53
|
st: 'start',
|
|
58
54
|
sy: 'sync',
|
|
59
55
|
rmi: 'registry:migrate',
|
|
60
|
-
sys: 'sync:scope',
|
|
61
56
|
g: 'git',
|
|
62
57
|
gp: 'pr',
|
|
63
|
-
prc: 'pr:classification',
|
|
64
|
-
fu: 'followup',
|
|
65
|
-
nf: 'notify',
|
|
66
|
-
obf: 'observations:flush',
|
|
67
|
-
mrr: 'measure:rerun',
|
|
68
58
|
ms: 'main:sync',
|
|
69
59
|
mm: 'main:merge',
|
|
70
60
|
bp: 'bump',
|
|
71
61
|
re: 'release',
|
|
72
|
-
res: 'release:scope',
|
|
73
62
|
v: 'version',
|
|
74
63
|
r: 'ranges',
|
|
75
64
|
dr: 'doctor',
|
|
@@ -77,107 +66,16 @@ const ALIASES: Record<string, string> = {
|
|
|
77
66
|
ad: 'adopt',
|
|
78
67
|
ov: 'overrides',
|
|
79
68
|
a: 'audit',
|
|
80
|
-
ap: 'audit:provision',
|
|
81
69
|
rt: 'reconcile-templates',
|
|
82
70
|
u: 'latest',
|
|
83
|
-
lg: 'latest:guard',
|
|
84
71
|
lc: 'latest:corepack',
|
|
85
72
|
lu: 'latest:update',
|
|
86
|
-
ls: 'latest:scope',
|
|
87
|
-
pm: 'prevent-main-commit',
|
|
88
|
-
cm: 'check-commit-message',
|
|
89
|
-
ss: 'secretlint-scan',
|
|
90
|
-
ppu: 'pre-push-unit',
|
|
91
|
-
rer: 'reserved-run',
|
|
92
|
-
ptc: 'pre-commit-type-check',
|
|
93
|
-
ird: 'issue:read',
|
|
94
|
-
ist: 'issue:state',
|
|
95
|
-
isc: 'issue:scout',
|
|
96
|
-
ifl: 'issue:file',
|
|
97
|
-
isf: 'issue:fold',
|
|
98
|
-
isfe: 'issue:fold-existing',
|
|
99
|
-
dfr: 'defect:rate',
|
|
100
|
-
ici: 'issue:cite',
|
|
101
|
-
icm: 'issue:comment',
|
|
102
|
-
pks: 'pkg:scout',
|
|
103
|
-
iln: 'issue:lint',
|
|
104
|
-
ibl: 'issue:backlinks',
|
|
105
|
-
rl: 'report:lint',
|
|
106
|
-
ep: 'epic',
|
|
107
|
-
ec: 'epic:check',
|
|
108
|
-
en: 'epic:next',
|
|
109
|
-
ea: 'epic:audit',
|
|
110
|
-
eb: 'epic:bundle',
|
|
111
|
-
ao: 'auto-ok:next',
|
|
112
|
-
bl: 'backlog:next',
|
|
113
|
-
blo: 'backlog:offer',
|
|
114
|
-
bld: 'backlog:drive',
|
|
115
|
-
blp: 'backlog:plan',
|
|
116
|
-
bls: 'backlog:stalled',
|
|
117
|
-
bb: 'backlog:budget',
|
|
118
|
-
co: 'cost',
|
|
119
|
-
ds: 'doc:section',
|
|
120
|
-
rs: 'read:set',
|
|
121
|
-
dcr: 'doc:read',
|
|
122
|
-
rf: 'read:files',
|
|
123
|
-
ef: 'edit:files',
|
|
124
|
-
rfs: 'refactor:scan',
|
|
125
73
|
tm: 'time',
|
|
126
74
|
tmd: 'time:density',
|
|
127
75
|
rtr: 'retrospective',
|
|
128
|
-
rb: 'review:brief',
|
|
129
|
-
r2: 'review:round2',
|
|
130
|
-
ra: 'review:attest',
|
|
131
|
-
rvr: 'review:record',
|
|
132
76
|
rvf: 'review:findings',
|
|
133
|
-
dp: 'disposition',
|
|
134
|
-
dg: 'delegate',
|
|
135
|
-
fo: 'fanout',
|
|
136
|
-
rh: 'run:hold',
|
|
137
|
-
rr: 'run:release',
|
|
138
|
-
rc: 'run:carry',
|
|
139
|
-
rtd: 'run:tidy',
|
|
140
|
-
rw: 'run:wake',
|
|
141
|
-
rct: 'run:cut',
|
|
142
|
-
rv: 'run:liveness',
|
|
143
|
-
red: 'run:ending',
|
|
144
|
-
rg: 'run:progress',
|
|
145
|
-
rp: 'run:prep',
|
|
146
|
-
ren: 'run:entry',
|
|
147
|
-
rtl: 'run:tail',
|
|
148
|
-
shp: 'ship',
|
|
149
|
-
rst: 'run:status',
|
|
150
|
-
rn: 'run:next',
|
|
151
|
-
rsp: 'run:step',
|
|
152
|
-
rpy: 'repo:party',
|
|
153
|
-
rmg: 'run:merge',
|
|
154
|
-
rrp: 'run:report',
|
|
155
|
-
rrv: 'run:review',
|
|
156
|
-
rev: 'run:event',
|
|
157
|
-
sp: 'stash:pop',
|
|
158
|
-
lno: 'lane:open',
|
|
159
|
-
lnc: 'lane:close',
|
|
160
|
-
lnl: 'lane:list',
|
|
161
|
-
lnp: 'lane:prune',
|
|
162
|
-
lnv: 'lane:output',
|
|
163
|
-
lnd: 'lane:dispatch',
|
|
164
|
-
lna: 'lane:await',
|
|
165
|
-
lnla: 'lane:launch',
|
|
166
|
-
rwg: 'run:watcher:guard',
|
|
167
|
-
rsd: 'run:stranded',
|
|
168
|
-
ig: 'investigation:guard',
|
|
169
|
-
drg: 'duplicate-read:guard',
|
|
170
|
-
rug: 'rule:guard',
|
|
171
77
|
ruv: 'rule:value',
|
|
172
|
-
ptg: 'pretool:guard',
|
|
173
|
-
sg: 'stop:guard',
|
|
174
78
|
ev: 'eval',
|
|
175
|
-
ol: 'oracle:list',
|
|
176
|
-
sa: 'split:assess',
|
|
177
|
-
shs: 'sonar:hotspots',
|
|
178
|
-
uir: 'ui:routes',
|
|
179
|
-
cs: 'clone:scan',
|
|
180
|
-
ca: 'cases',
|
|
181
79
|
}
|
|
182
80
|
|
|
183
81
|
// The canonical name of a `josh` subcommand: an alias expands, and anything else passes through
|
|
@@ -66,7 +66,7 @@ const DEV_COMMANDS: Record<string, CommandEntry> = {
|
|
|
66
66
|
reference: ['', 'developer', ['files', 'processes']],
|
|
67
67
|
},
|
|
68
68
|
'format:edited': {
|
|
69
|
-
script: 'scripts/hooks/format-edited-
|
|
69
|
+
script: 'scripts/hooks/format-edited-cli.ts',
|
|
70
70
|
description: 'Claude Code hook: format the file just edited (reads the tool call on stdin)',
|
|
71
71
|
category: 'Development',
|
|
72
72
|
reference: ['', 'automation', ['files', 'processes']],
|
|
@@ -85,7 +85,7 @@ const DEV_COMMANDS: Record<string, CommandEntry> = {
|
|
|
85
85
|
// node's own `--env-file` parser with node's own precedence.
|
|
86
86
|
},
|
|
87
87
|
'pretool:guard': {
|
|
88
|
-
script: 'scripts/hooks/pretool-guard.ts',
|
|
88
|
+
script: 'scripts/hooks/pretool-guard-cli.ts',
|
|
89
89
|
description:
|
|
90
90
|
'Claude Code hook: the batch, investigation and rule guards in one process (reads the tool call on stdin)',
|
|
91
91
|
category: 'Development',
|
|
@@ -17,7 +17,7 @@ const PROJECT_COMMANDS: Record<string, CommandEntry> = {
|
|
|
17
17
|
description: 'Set a project up for the GitHub Issue workflow, from git init to the setup PR',
|
|
18
18
|
category: 'Project',
|
|
19
19
|
reference: [
|
|
20
|
-
`${PROFILE_ARGUMENTS} [--yes] [--github] [--public]`,
|
|
20
|
+
`${PROFILE_ARGUMENTS} [--yes] [--github] [--public] [--init-command <command>]`,
|
|
21
21
|
'developer',
|
|
22
22
|
['files', 'git', 'network', 'processes'],
|
|
23
23
|
],
|
|
@@ -19,6 +19,15 @@ import { lane_paths } from './lane-paths'
|
|
|
19
19
|
// `.claude/skills/workflow-commands/pre-gate-cut.md`.
|
|
20
20
|
const KEY = 'JOSH_LANE_CHILD'
|
|
21
21
|
|
|
22
|
+
// **A dispatched child also asks for the five-minute prompt-cache TTL** (joshuafolkken/kit#2944). A
|
|
23
|
+
// headless `claude -p` session on a subscription defaults to the one-hour TTL, whose writes bill at 2x
|
|
24
|
+
// the input rate against 1.25x for five minutes. A replay of 1,953 lane transcripts (about 57,000
|
|
25
|
+
// requests) found 0.3% of request gaps over five minutes, so the shorter TTL — re-writing the prefix on
|
|
26
|
+
// those few gaps — still costs 12-13% less. Every launcher of a child composes its environment here,
|
|
27
|
+
// so this is the one place the choice is made.
|
|
28
|
+
const CACHE_TTL_KEY = 'CLAUDE_CODE_PROMPT_CACHE_TTL'
|
|
29
|
+
const CACHE_TTL = '5m'
|
|
30
|
+
|
|
22
31
|
type MarkerSource = Readonly<Record<string, string | undefined>>
|
|
23
32
|
|
|
24
33
|
// The environment fragment that marks a child dispatched for `#<issue>`. Composed from a digits-only
|
|
@@ -27,7 +36,7 @@ type MarkerSource = Readonly<Record<string, string | undefined>>
|
|
|
27
36
|
function environment_for(issue: string): Record<string, string> {
|
|
28
37
|
run_issue_number.require_issue_number(issue)
|
|
29
38
|
|
|
30
|
-
return { [KEY]: issue }
|
|
39
|
+
return { [KEY]: issue, [CACHE_TTL_KEY]: CACHE_TTL }
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
// The issue a child was dispatched for, or `undefined` when the mark is absent or malformed. A blank
|
|
@@ -52,7 +61,12 @@ function is_child_of(directory: string, source: MarkerSource = process.env): boo
|
|
|
52
61
|
return marked !== undefined && marked === lane_paths.lane_issue_of(directory, { ...source })
|
|
53
62
|
}
|
|
54
63
|
|
|
55
|
-
const lane_child_marker = {
|
|
64
|
+
const lane_child_marker = {
|
|
65
|
+
KEY,
|
|
66
|
+
env_for: environment_for,
|
|
67
|
+
is_child_of,
|
|
68
|
+
marked_issue,
|
|
69
|
+
}
|
|
56
70
|
|
|
57
71
|
export type { MarkerSource }
|
|
58
72
|
export { lane_child_marker }
|