@joshuafolkken/kit 1.1094.0 → 1.1136.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/agents/investigator.md +17 -0
- package/.claude/settings.json +1 -0
- package/.claude/skills/workflow-commands/SKILL.md +1 -1
- package/.claude/skills/workflow-commands/background-commands.md +36 -85
- package/.claude/skills/workflow-commands/backlogrun-progress.md +31 -34
- package/.claude/skills/workflow-commands/backlogrun-steps.md +2 -2
- package/.claude/skills/workflow-commands/delegation.md +5 -1
- package/.claude/skills/workflow-commands/fullrun.md +5 -4
- package/.claude/skills/workflow-commands/halfrun.md +5 -4
- package/.claude/skills/workflow-commands/latest-gate.md +7 -15
- package/.claude/skills/workflow-commands/pre-gate-cut.md +4 -8
- package/.claude/skills/workflow-commands/split-assessment.md +4 -18
- package/.github/actions/setup-node/action.yml +33 -0
- package/.github/actions/setup-pnpm/action.yml +4 -4
- package/.github/workflows/auto-tag.yml +2 -3
- package/.github/workflows/ci.yml +7 -8
- package/.github/workflows/dependabot-auto-merge.yml +1 -0
- package/.github/workflows/pr-classification.yml +1 -3
- package/.github/workflows/production.yml +1 -0
- package/.github/workflows/publish.yml +10 -9
- package/.github/workflows/sonar-qube.yml +17 -13
- package/.pnpmfile.mjs +29 -0
- package/AGENTS.md +4 -11
- package/CLAUDE.md +24 -17
- package/GEMINI.md +4 -13
- package/README.md +8 -3
- package/SECURITY.md +2 -0
- package/cspell/index.yaml +4 -0
- package/dist/CLAUDE.basic.md +6 -6
- package/dist/CLAUDE.md +24 -17
- package/dist/CLAUDE.static.md +6 -6
- package/dist/config-merge/index.js +33 -9
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-OIZUHPJI.js → chunk-FO2I7UT7.js} +1 -1
- package/dist/hooks/{chunk-NKOG3XAV.js → chunk-GDU26K3J.js} +11 -3
- package/dist/hooks/{chunk-O4LL4HTI.js → chunk-HANSKHES.js} +2 -2
- package/dist/hooks/{chunk-3C4YCCEF.js → chunk-HBKZLITF.js} +4 -4
- package/dist/hooks/{chunk-UD24EAIL.js → chunk-JKKTVDAG.js} +1984 -146
- package/dist/hooks/{chunk-M2XDCUKD.js → chunk-KBXZLMRH.js} +89 -1803
- package/dist/hooks/{chunk-IL27YTPH.js → chunk-KOKOAV6D.js} +2 -2
- package/dist/hooks/{chunk-7ZPFAVR7.js → chunk-LJBLGHEH.js} +59 -19
- package/dist/hooks/{chunk-E54KGFEQ.js → chunk-NFZGFGOR.js} +1 -1
- package/dist/hooks/{chunk-KHWHKJOM.js → chunk-RFWWUI5F.js} +4 -4
- package/dist/hooks/codex-hook-adapter.js +12 -47
- package/dist/hooks/duplicate-read-guard.js +5 -5
- package/dist/hooks/format-edited.js +2 -2
- package/dist/hooks/inputs.json +175 -173
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +9 -9
- package/dist/hooks/stop-guard.js +17 -12
- package/dist/josh.js +87 -31
- package/dist/version/index.js +36 -20
- package/eslint/rules/unicorn.js +3 -2
- package/package.json +23 -5
- package/pnpm-workspace.yaml +5 -7
- package/prompts/collaboration-workflow/glossary.md +17 -0
- package/prompts/collaboration-workflow/issue-template.md +1 -1
- package/prompts/collaboration-workflow/operating-rules.md +1 -1
- package/prompts/collaboration-workflow/principles.md +9 -0
- package/prompts/collaboration-workflow.md +1 -0
- package/scripts/backlog/backlog-arrival.ts +30 -10
- package/scripts/backlog/backlog-budget-cli.ts +15 -16
- package/scripts/backlog/backlog-offer-cli.ts +6 -14
- package/scripts/backlog/backlog-plan.ts +29 -18
- package/scripts/backlog/backlog-ready.ts +20 -7
- package/scripts/config-merge/parse-jsonc.ts +33 -3
- package/scripts/config-merge/patch-json-key.ts +13 -6
- package/scripts/cost-runtime/cost-cli.ts +3 -7
- package/scripts/delegation/delegation-policy.ts +9 -1
- package/scripts/delegation/investigation-guard.ts +1 -1
- package/scripts/delegation/investigation-reads.ts +105 -19
- package/scripts/doctor/doctor-consumer.ts +3 -5
- package/scripts/doctor/doctor-io.ts +9 -13
- package/scripts/document/backlogrun-parent-read-set.ts +12 -1
- package/scripts/document/capped-print.ts +74 -0
- package/scripts/document/document-byte-budget.ts +1 -0
- package/scripts/document/document-section-cli.ts +4 -3
- package/scripts/document/entry-read-budget.ts +13 -7
- package/scripts/document/entry-read-set.ts +32 -8
- package/scripts/document/lane-child-read-set.ts +7 -0
- package/scripts/document/read-set-point-of-use.ts +8 -3
- package/scripts/document/read-set-trim.ts +3 -1
- package/scripts/epic/epic-audit.ts +3 -18
- package/scripts/gate/scoped-green.ts +25 -14
- package/scripts/gh/git-gh-issue-list.ts +12 -11
- package/scripts/gh/git-gh-issue-write.ts +4 -1
- package/scripts/gh/git-gh-pr-auto-merge.ts +4 -1
- package/scripts/git/git-command.ts +6 -1
- package/scripts/git/git-spawn-sync.ts +65 -0
- package/scripts/git/git-spawn.ts +5 -4
- package/scripts/git/stash/stash-pop-args.ts +1 -6
- package/scripts/init/claude-kit-only-settings.ts +29 -0
- package/scripts/init/init-copy-content.ts +8 -2
- package/scripts/init/init-file-action.ts +54 -0
- package/scripts/init/init-logic-migrate.ts +35 -17
- package/scripts/init/init-logic-secretlint.ts +1 -1
- package/scripts/init/init-logic.ts +6 -22
- package/scripts/init/init.ts +11 -68
- package/scripts/init/initial-commit.ts +24 -0
- package/scripts/init/kit-written-paths.ts +2 -0
- package/scripts/init/project-profile.ts +3 -6
- package/scripts/init/start-exec.ts +16 -3
- package/scripts/init/start-steps.ts +10 -13
- package/scripts/issue/defect-rate-cli.ts +61 -20
- package/scripts/issue/issue-backlinks.ts +7 -14
- package/scripts/issue/issue-cite.ts +12 -6
- package/scripts/issue/issue-file.ts +5 -12
- package/scripts/issue/issue-read-cli.ts +1 -0
- package/scripts/issue/issue-report-failures.ts +6 -2
- package/scripts/issue/issue-state-cli.ts +1 -1
- package/scripts/issue/markdown-section.ts +21 -6
- package/scripts/issue/measure-rerun-cli.ts +73 -14
- package/scripts/josh/josh-commands-lane.ts +1 -1
- package/scripts/josh/josh-commands-project.ts +8 -1
- package/scripts/josh/josh-commands-workflow.ts +1 -1
- package/scripts/josh/run-github-prerequisite.ts +3 -6
- package/scripts/lane/lane-await.ts +89 -15
- package/scripts/lane/lane-cli.ts +11 -13
- package/scripts/lane/lane-close.ts +4 -1
- package/scripts/lane/lane-launch-cli.ts +10 -16
- package/scripts/lane/openai-review-broker.ts +4 -1
- package/scripts/lib/cli-flags.ts +31 -2
- package/scripts/lib/error-message.ts +12 -1
- package/scripts/lines/line-targets.ts +0 -11
- package/scripts/lines/lines-command.ts +5 -3
- package/scripts/propagate/propagate-git.ts +3 -8
- package/scripts/refactor/refactor-scan.ts +2 -1
- package/scripts/registry-migration/migrate-logic.ts +28 -19
- package/scripts/registry-migration/migrate.ts +6 -5
- package/scripts/release/github-release-cli.ts +14 -3
- package/scripts/release/github-release-environment.ts +1 -0
- package/scripts/release/github-release.ts +103 -17
- package/scripts/release/release-publish.ts +36 -2
- package/scripts/release/release-worktree.ts +97 -2
- package/scripts/review/review-checkout.ts +3 -2
- package/scripts/review/review-record-cli.ts +9 -16
- package/scripts/rules/early-heartbeat.ts +4 -3
- package/scripts/rules/lane-park.ts +9 -0
- package/scripts/rules/stop-rules.ts +7 -4
- package/scripts/run/run-carry-args.ts +8 -12
- package/scripts/run/run-carry-cli.ts +3 -1
- package/scripts/run/run-carry-conversation.ts +94 -0
- package/scripts/run/run-carry-reclaim.ts +37 -0
- package/scripts/run/run-carry.ts +21 -5
- package/scripts/run/run-cut-args.ts +1 -6
- package/scripts/run/run-ending-cli.ts +1 -5
- package/scripts/run/run-entry-cli.ts +40 -9
- package/scripts/run/run-entry-stop.ts +104 -0
- package/scripts/run/run-event-stream-emit.ts +7 -2
- package/scripts/run/run-hold.ts +4 -1
- package/scripts/run/run-liveness-cli.ts +1 -5
- package/scripts/run/run-merge-cli.ts +2 -10
- package/scripts/run/run-merge-steps.ts +4 -1
- package/scripts/run/run-parent-cut-hook.ts +4 -1
- package/scripts/run/run-prep-cli.ts +4 -1
- package/scripts/run/run-progress-args.ts +20 -4
- package/scripts/run/run-progress-cli.ts +31 -29
- package/scripts/run/run-progress-clock.ts +4 -1
- package/scripts/run/run-ship-cli.ts +8 -16
- package/scripts/run/run-ship-probe.ts +4 -1
- package/scripts/run/run-status-cli.ts +2 -15
- package/scripts/run/run-tidy-lanes.ts +4 -1
- package/scripts/run/run-wake-cli.ts +2 -10
- package/scripts/run/run-watcher-guard.ts +9 -5
- package/scripts/safe-chain/pack-hook.ts +92 -0
- package/scripts/sync/sync-ai-files.ts +4 -0
- package/scripts/test/test-declared-changed.ts +13 -16
- package/scripts/test/test-declared.ts +3 -7
- package/scripts/test/test-related.ts +1 -6
- package/scripts/test/test-unit-guard.ts +21 -7
- package/scripts/time-runtime/time-spans.ts +4 -0
- package/scripts/version/version-commands.ts +11 -1
- package/templates/CLAUDE.basic.md +6 -6
- package/templates/workflows/ci.yml +16 -82
- package/templates/workflows/dependabot-auto-merge.yml +1 -0
- package/templates/workflows/github-release.yml +14 -28
- package/tsconfig.sonar.json +4 -22
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { start_exec } from './start-exec'
|
|
2
|
+
|
|
3
|
+
// The first commit of a new repository, shared by `josh start` and `josh dogfood:commit` so neither
|
|
4
|
+
// keeps a copy of how it is made.
|
|
5
|
+
|
|
6
|
+
const DEFAULT_BRANCH = 'main'
|
|
7
|
+
|
|
8
|
+
function create_repository(root: string): void {
|
|
9
|
+
start_exec.run('git', ['init', `--initial-branch=${DEFAULT_BRANCH}`], root)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function commit_all(root: string): void {
|
|
13
|
+
start_exec.run('git', ['add', '--all'], root)
|
|
14
|
+
// The first commit of a new repository has no branch to come from, so the hook that keeps commits
|
|
15
|
+
// off main — installed by the initialize step in a full project — would refuse the only commit
|
|
16
|
+
// that has to land there.
|
|
17
|
+
start_exec.run('git', ['commit', '--no-verify', '--message', 'Initial commit'], root)
|
|
18
|
+
// A repository created by an older `git init` may name its unborn branch `master`.
|
|
19
|
+
start_exec.run('git', ['branch', '--move', '--force', DEFAULT_BRANCH], root)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const initial_commit = { create_repository, commit_all }
|
|
23
|
+
|
|
24
|
+
export { initial_commit }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PACK_HOOK_FILE } from '#scripts/safe-chain/pack-hook'
|
|
1
2
|
import { PROJECT_CONFIG_FILE } from '#scripts/safe-chain/project-config'
|
|
2
3
|
import { managed_config_scope } from '#scripts/sync/managed-config-scope'
|
|
3
4
|
import { BASIC_PRETTIER_CONFIG } from './init-actions'
|
|
@@ -13,6 +14,7 @@ const SETUP_ONLY_PATHS: ReadonlySet<string> = new Set([
|
|
|
13
14
|
CLAUDE_MD_FILENAME,
|
|
14
15
|
BASIC_PRETTIER_CONFIG,
|
|
15
16
|
PROJECT_CONFIG_FILE,
|
|
17
|
+
PACK_HOOK_FILE,
|
|
16
18
|
MANIFEST_FILE,
|
|
17
19
|
LOCKFILE,
|
|
18
20
|
])
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, type Dirent } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { repo_origin } from '#scripts/discovery/repo-origin'
|
|
4
|
+
import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
|
|
4
5
|
import { json_value } from '#scripts/lib/json-value'
|
|
5
6
|
import { KIT_PACKAGE_NAME } from '#scripts/version/kit-descriptor'
|
|
6
|
-
import { execaSync } from 'execa'
|
|
7
7
|
|
|
8
8
|
type ProjectProfile = 'basic' | 'full'
|
|
9
9
|
|
|
@@ -159,12 +159,9 @@ function scan_files(root: string): { has_web: boolean; has_typescript: boolean }
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
function has_github_remote(root: string): boolean {
|
|
162
|
-
const
|
|
163
|
-
cwd: root,
|
|
164
|
-
reject: false,
|
|
165
|
-
})
|
|
162
|
+
const url = git_spawn_sync.origin_url(root)
|
|
166
163
|
|
|
167
|
-
return
|
|
164
|
+
return url !== undefined && repo_origin.parse_origin_url(url) !== undefined
|
|
168
165
|
}
|
|
169
166
|
|
|
170
167
|
function inspect_project(root: string, requested?: ProjectProfile): ProjectShape {
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { git_gh_exec } from '#scripts/gh/git-gh-exec'
|
|
2
|
+
import { git_location_environment } from '#scripts/git/git-location-environment'
|
|
2
3
|
import { execaSync } from 'execa'
|
|
3
4
|
|
|
4
5
|
// The three spawn shapes every `josh start` step uses, shared by `start-steps.ts`, the setup pull
|
|
5
6
|
// request step and the `josh init` hint so none of them keeps a copy of its own.
|
|
7
|
+
//
|
|
8
|
+
// The three git-driving shapes below act on `root`, so `cwd` has to mean it: an inherited `GIT_DIR` and friends beat `cwd`
|
|
9
|
+
// and would carry a `git init` or commit into whichever repository a calling hook was firing in
|
|
10
|
+
// (joshuafolkken/kit#3151).
|
|
11
|
+
function root_environment(): Record<string, string | undefined> {
|
|
12
|
+
return git_location_environment.location_free_environment()
|
|
13
|
+
}
|
|
14
|
+
|
|
6
15
|
function succeeds(command: string, args: ReadonlyArray<string>, root: string): boolean {
|
|
7
|
-
return
|
|
16
|
+
return (
|
|
17
|
+
execaSync(command, args, { cwd: root, env: root_environment(), reject: false }).exitCode === 0
|
|
18
|
+
)
|
|
8
19
|
}
|
|
9
20
|
|
|
10
21
|
function read_output(
|
|
@@ -12,7 +23,7 @@ function read_output(
|
|
|
12
23
|
args: ReadonlyArray<string>,
|
|
13
24
|
root: string,
|
|
14
25
|
): string | undefined {
|
|
15
|
-
const result = execaSync(command, args, { cwd: root, reject: false })
|
|
26
|
+
const result = execaSync(command, args, { cwd: root, env: root_environment(), reject: false })
|
|
16
27
|
|
|
17
28
|
return result.exitCode === 0 ? result.stdout.trim() : undefined
|
|
18
29
|
}
|
|
@@ -20,7 +31,9 @@ function read_output(
|
|
|
20
31
|
// Every step spawned here dials GitHub directly, past a scanner's loopback proxy, the same as every
|
|
21
32
|
// other `gh` spawn under scripts/ (joshuafolkken/kit#2436); a local `git` step is unaffected by it.
|
|
22
33
|
function run(command: string, args: ReadonlyArray<string>, root: string): void {
|
|
23
|
-
|
|
34
|
+
const environment = { ...root_environment(), ...git_gh_exec.direct_environment().env }
|
|
35
|
+
|
|
36
|
+
execaSync(command, args, { env: environment, cwd: root, stdio: 'inherit' })
|
|
24
37
|
}
|
|
25
38
|
|
|
26
39
|
// A caller's own initialize command stands in for the in-process `josh init`, so it keeps the
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
|
+
import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
|
|
2
3
|
import { error_text } from '#scripts/lib/error-message'
|
|
3
4
|
import { repository_labels } from '#scripts/repo/repository-labels'
|
|
4
5
|
import { self_sync_guard } from '#scripts/self-sync-guard/self-sync-guard-logic'
|
|
5
6
|
import { parseCommandString } from 'execa'
|
|
6
7
|
import { main as init_main } from './init'
|
|
7
8
|
import { PACKAGE_DIR } from './init-paths'
|
|
9
|
+
import { initial_commit } from './initial-commit'
|
|
8
10
|
import { kit_setup_state } from './kit-setup-state'
|
|
9
11
|
import type { ProjectProfile, ProjectShape } from './project-profile'
|
|
10
12
|
import { start_exec } from './start-exec'
|
|
@@ -22,7 +24,6 @@ interface RunContext extends StepContext {
|
|
|
22
24
|
baseline: ReadonlyArray<string> | undefined
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
const DEFAULT_BRANCH = 'main'
|
|
26
27
|
const GH_INSTALL_HINT =
|
|
27
28
|
'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.'
|
|
28
29
|
const GH_AUTH_HINT =
|
|
@@ -44,7 +45,7 @@ function read_git_state(root: string, shape: ProjectShape): GitState {
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
const has_origin =
|
|
48
|
+
const has_origin = git_spawn_sync.origin_url(root) !== undefined
|
|
48
49
|
const branch = read_output('git', ['symbolic-ref', '--short', 'HEAD'], root)
|
|
49
50
|
const has_commits = succeeds('git', ['rev-parse', '--verify', '--quiet', 'HEAD'], root)
|
|
50
51
|
const has_kit_committed = kit_setup_state.is_kit_committed(root)
|
|
@@ -69,7 +70,7 @@ function self_run_refusal(root: string): string | undefined {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
function create_git_repository(context: StepContext): void {
|
|
72
|
-
|
|
73
|
+
initial_commit.create_repository(context.root)
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
// The same setup `josh init` runs, called rather than copied, with the profile the user confirmed.
|
|
@@ -89,13 +90,7 @@ function initialize(context: StepContext): void {
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
function commit_all(context: StepContext): void {
|
|
92
|
-
|
|
93
|
-
// The first commit of a new repository has no branch to come from, so the hook that keeps commits
|
|
94
|
-
// off main — installed by the initialize step in a full project — would refuse the only commit
|
|
95
|
-
// that has to land there.
|
|
96
|
-
run('git', ['commit', '--no-verify', '--message', 'Initial commit'], context.root)
|
|
97
|
-
// A repository created by an older `git init` may name its unborn branch `master`.
|
|
98
|
-
run('git', ['branch', '--move', '--force', DEFAULT_BRANCH], context.root)
|
|
93
|
+
initial_commit.commit_all(context.root)
|
|
99
94
|
}
|
|
100
95
|
|
|
101
96
|
function create_github_repository(context: StepContext): void {
|
|
@@ -118,13 +113,15 @@ async function open_setup_pull_request(context: RunContext): Promise<void> {
|
|
|
118
113
|
await start_setup_pr.open(context.root, context.baseline)
|
|
119
114
|
}
|
|
120
115
|
|
|
121
|
-
// What was already changed before
|
|
122
|
-
//
|
|
116
|
+
// What was already changed before the setup ran, read whenever a setup pull request will follow, so
|
|
117
|
+
// it can tell the setup's files from the user's own (#2872). Kit's own `josh init` needs it as much as
|
|
118
|
+
// a caller's command: its `josh format` rewrites the project's own sources, which kit cannot name, and
|
|
119
|
+
// leaving them out failed the pull request's Prettier check (joshuafolkken/kit#3136).
|
|
123
120
|
function baseline_of(
|
|
124
121
|
steps: ReadonlyArray<StartStep>,
|
|
125
122
|
context: StepContext,
|
|
126
123
|
): ReadonlyArray<string> | undefined {
|
|
127
|
-
if (
|
|
124
|
+
if (!steps.includes('setup_pr')) return undefined
|
|
128
125
|
|
|
129
126
|
return start_setup_pr.changed_paths(context.root)
|
|
130
127
|
}
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
-
import { parseArgs } from 'node:util'
|
|
4
3
|
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
5
4
|
import { git_gh_exec } from '#scripts/gh/git-gh-exec'
|
|
6
|
-
import {
|
|
5
|
+
import { parse_json } from '#scripts/git/parse-json-array'
|
|
7
6
|
import { issue_label_schema } from '#scripts/git/schemas'
|
|
7
|
+
import { bounded_pool } from '#scripts/lib/bounded-pool'
|
|
8
|
+
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
8
9
|
import { z } from 'zod'
|
|
9
10
|
import { defect_rate, type DefectRate, type RateIssue } from './defect-rate'
|
|
10
11
|
|
|
11
12
|
const SUCCESS_EXIT_CODE = 0
|
|
12
13
|
const FAILURE_EXIT_CODE = 1
|
|
13
14
|
const PER_PAGE = '100'
|
|
15
|
+
const FIRST_PAGE = 1
|
|
16
|
+
const SECOND_PAGE = 2
|
|
17
|
+
// The search API serves at most this many results, so a window past it holds no further page.
|
|
18
|
+
const SEARCH_RESULT_CEILING = 1000
|
|
19
|
+
// The search API allows 30 requests a minute; both searches together stay at the ~10 requests the
|
|
20
|
+
// sequential walk made, so this bounds only how many are in flight at once.
|
|
21
|
+
const PAGE_CONCURRENCY = 4
|
|
14
22
|
const USAGE = 'Usage: pnpm josh defect:rate [--days <n>]'
|
|
15
23
|
const UNKNOWN_REPO_MESSAGE =
|
|
16
24
|
'Could not read this repository from `git remote`, so no issue can be searched — check `gh auth status` and that this is a checkout with an `origin` remote.'
|
|
@@ -33,7 +41,7 @@ const search_item_schema = z.object({
|
|
|
33
41
|
labels: z.array(issue_label_schema),
|
|
34
42
|
})
|
|
35
43
|
|
|
36
|
-
// One page of `search/issues`.
|
|
44
|
+
// One page of `search/issues`.
|
|
37
45
|
const search_page_schema = z.object({
|
|
38
46
|
total_count: z.number(),
|
|
39
47
|
incomplete_results: z.boolean(),
|
|
@@ -48,22 +56,60 @@ interface SearchResult {
|
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
function read_days(argv: ReadonlyArray<string>): number | undefined {
|
|
51
|
-
|
|
52
|
-
const { values } = parseArgs({ args: [...argv], options: OPTIONS, strict: true })
|
|
53
|
-
const raw = values.days ?? String(defect_rate.DEFAULT_WINDOW_DAYS)
|
|
59
|
+
const values = cli_flags.values_of(argv, OPTIONS)
|
|
54
60
|
|
|
55
|
-
|
|
61
|
+
if (values === undefined) return undefined
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
const raw = values.days ?? String(defect_rate.DEFAULT_WINDOW_DAYS)
|
|
58
64
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
if (!DAYS_PATTERN.test(raw)) return undefined
|
|
66
|
+
|
|
67
|
+
const days = Number(raw)
|
|
68
|
+
|
|
69
|
+
return days <= MAX_WINDOW_DAYS ? days : undefined
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function search_path(query: string, page: number = FIRST_PAGE): string {
|
|
73
|
+
const parameters = new URLSearchParams({ q: query, per_page: PER_PAGE })
|
|
74
|
+
|
|
75
|
+
if (page !== FIRST_PAGE) parameters.set('page', String(page))
|
|
76
|
+
|
|
77
|
+
return `search/issues?${parameters.toString()}`
|
|
63
78
|
}
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
// How many pages the search holds, read off the first page's own count. Bounded by the API's
|
|
81
|
+
// ceiling, past which GitHub serves no page at all — so this asks for exactly the pages `gh api
|
|
82
|
+
// --paginate` used to walk to.
|
|
83
|
+
function page_count(first: SearchPage): number {
|
|
84
|
+
return Math.ceil(Math.min(first.total_count, SEARCH_RESULT_CEILING) / Number(PER_PAGE))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function read_page(query: string, page: number): Promise<SearchPage | undefined> {
|
|
88
|
+
const raw = await git_gh_exec.exec_gh_api({ path: search_path(query, page) })
|
|
89
|
+
|
|
90
|
+
return parse_json.parse_json_object_safe(raw, search_page_schema)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Every page of one search. joshuafolkken/kit#3103: `--paginate` follows each page's `next` link, so
|
|
94
|
+
// a two-week window of ~5 pages cost ~1.5 s a page, one after another, on every `backlog:plan`. The
|
|
95
|
+
// first page names the total, so the rest are fetched together; any page that does not read fails
|
|
96
|
+
// the whole search, as a failed `--paginate` did.
|
|
97
|
+
async function read_pages(query: string): Promise<ReadonlyArray<SearchPage> | undefined> {
|
|
98
|
+
const first = await read_page(query, FIRST_PAGE)
|
|
99
|
+
|
|
100
|
+
if (first === undefined) return undefined
|
|
101
|
+
|
|
102
|
+
const rest_numbers = Array.from(
|
|
103
|
+
{ length: Math.max(page_count(first) - FIRST_PAGE, 0) },
|
|
104
|
+
(_value, index) => index + SECOND_PAGE,
|
|
105
|
+
)
|
|
106
|
+
const rest = await bounded_pool.bounded_map(
|
|
107
|
+
rest_numbers,
|
|
108
|
+
PAGE_CONCURRENCY,
|
|
109
|
+
async (page) => await read_page(query, page),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return rest.every((page) => page !== undefined) ? [first, ...rest] : undefined
|
|
67
113
|
}
|
|
68
114
|
|
|
69
115
|
function to_result(pages: ReadonlyArray<SearchPage>): SearchResult {
|
|
@@ -83,12 +129,7 @@ function to_result(pages: ReadonlyArray<SearchPage>): SearchResult {
|
|
|
83
129
|
|
|
84
130
|
async function search(query: string): Promise<SearchResult | undefined> {
|
|
85
131
|
try {
|
|
86
|
-
const
|
|
87
|
-
path: search_path(query),
|
|
88
|
-
should_paginate: true,
|
|
89
|
-
should_slurp: true,
|
|
90
|
-
})
|
|
91
|
-
const pages = parse_json_array_or_undefined(raw, search_page_schema)
|
|
132
|
+
const pages = await read_pages(query)
|
|
92
133
|
|
|
93
134
|
return pages === undefined ? undefined : to_result(pages)
|
|
94
135
|
} catch {
|
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
// undetected. This classifies an origin issue's filed backlinks into one of four fixed words
|
|
5
5
|
// (joshuafolkken/kit#2123). The headings are the single source's, pinned by the document test.
|
|
6
6
|
|
|
7
|
+
import { markdown_section } from './markdown-section'
|
|
8
|
+
|
|
7
9
|
const ORIGIN_HEADING = '## Origin'
|
|
8
10
|
const UPSTREAM_ISSUES_HEADING = '## Upstream issues'
|
|
9
11
|
const UPSTREAM_CANDIDATE_HEADING = '## Upstream candidate'
|
|
10
12
|
const CHECKBOX_PATTERN = /^\s*-\s*\[[ xX]\]/u
|
|
11
13
|
const WRONG_UPSTREAM_HEADING_PATTERN = /^##\s+Upstream\b/iu
|
|
12
|
-
const SUBHEADING_PATTERN = /^##\s/u
|
|
13
14
|
const BARE_REFERENCE_PATTERN = /^#\d+$/u
|
|
14
15
|
const DIGITS_PATTERN = /^\d+$/u
|
|
15
16
|
const REFERENCE_MARK = '#'
|
|
@@ -27,31 +28,23 @@ interface UpstreamEntry {
|
|
|
27
28
|
body: string
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
return body.split('\n').map((line) => line.trimEnd())
|
|
32
|
-
}
|
|
33
|
-
|
|
31
|
+
// A heading inside a code fence is example text, so neither lookup counts one (joshuafolkken/kit#3066).
|
|
34
32
|
function has_heading(body: string, heading: string): boolean {
|
|
35
|
-
return
|
|
33
|
+
return markdown_section.has_line(body, heading)
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
// The lines under `heading`, up to the next `##` heading; `undefined` when the heading is absent.
|
|
39
37
|
function section_lines(body: string, heading: string): ReadonlyArray<string> | undefined {
|
|
40
|
-
|
|
41
|
-
const start = lines.findIndex((line) => line.trim() === heading)
|
|
42
|
-
if (start === -1) return undefined
|
|
43
|
-
|
|
44
|
-
const rest = lines.slice(start + 1)
|
|
45
|
-
const end = rest.findIndex((line) => SUBHEADING_PATTERN.test(line.trim()))
|
|
38
|
+
if (!has_heading(body, heading)) return undefined
|
|
46
39
|
|
|
47
|
-
return
|
|
40
|
+
return markdown_section.section_lines(body, heading)
|
|
48
41
|
}
|
|
49
42
|
|
|
50
43
|
// A heading that looks like the upstream one but is not exactly it (`## Upstream`, `## Upstreams`,
|
|
51
44
|
// wrong case) — the grep the template promises would miss it, so it is a wrong-heading rather than a
|
|
52
45
|
// missing one. `## Upstream candidate` is a legitimate separate heading and is excluded.
|
|
53
46
|
function has_wrong_upstream_heading(body: string): boolean {
|
|
54
|
-
return
|
|
47
|
+
return markdown_section.unfenced_lines(body).some((line) => {
|
|
55
48
|
const heading = line.trim()
|
|
56
49
|
if (heading === UPSTREAM_ISSUES_HEADING || heading === UPSTREAM_CANDIDATE_HEADING) return false
|
|
57
50
|
|
|
@@ -46,14 +46,19 @@ function citation_line(slug: string, number: string, summary: string): string {
|
|
|
46
46
|
// and the plain `#N` only when the repository could not be read at all.
|
|
47
47
|
type IssueCiter = (number: string) => string
|
|
48
48
|
|
|
49
|
+
// One issue, named from what the caller holds — the single decision every listing's citation goes
|
|
50
|
+
// through (joshuafolkken/kit#3099), so a renderer holding one title never re-derives the fallback.
|
|
51
|
+
function reference(slug: string | undefined, number: string, title: string | undefined): string {
|
|
52
|
+
if (slug === undefined) return `#${number}`
|
|
53
|
+
|
|
54
|
+
return title === undefined
|
|
55
|
+
? `[#${number}](${issue_url(slug, number)})`
|
|
56
|
+
: citation_line(slug, number, title)
|
|
57
|
+
}
|
|
58
|
+
|
|
49
59
|
function citer(slug: string | undefined, titles: ReadonlyMap<string, string>): IssueCiter {
|
|
50
60
|
return function cite(number: string): string {
|
|
51
|
-
|
|
52
|
-
const title = titles.get(number)
|
|
53
|
-
|
|
54
|
-
return title === undefined
|
|
55
|
-
? `[#${number}](${issue_url(slug, number)})`
|
|
56
|
-
: citation_line(slug, number, title)
|
|
61
|
+
return reference(slug, number, titles.get(number))
|
|
57
62
|
}
|
|
58
63
|
}
|
|
59
64
|
|
|
@@ -111,6 +116,7 @@ const issue_cite = {
|
|
|
111
116
|
missing_line,
|
|
112
117
|
no_repo_line,
|
|
113
118
|
parse_target,
|
|
119
|
+
reference,
|
|
114
120
|
unreadable_line,
|
|
115
121
|
}
|
|
116
122
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { parseArgs } from 'node:util'
|
|
1
|
+
import type { parseArgs } from 'node:util'
|
|
2
2
|
import { epic_issue } from '#scripts/epic/epic-issue'
|
|
3
3
|
import { github_issue_url } from '#scripts/gh/github-issue-url'
|
|
4
|
+
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
4
5
|
import { issue_backlinks } from './issue-backlinks'
|
|
5
6
|
import { issue_classification } from './issue-classification'
|
|
6
7
|
import { DEPTH_LABEL_ORDER, FILING_ROUTE_LABELS, has_label_name } from './issue-labels'
|
|
@@ -103,19 +104,11 @@ function arguments_of(values: ParsedValues, title: string | undefined): FileArgu
|
|
|
103
104
|
return { title, body_file, ...labelled, labels: values.label ?? [], repo: values.repo }
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
// An unknown flag
|
|
107
|
+
// An unknown flag is `undefined` rather than a throw, so the answer is the usage line, not a stack trace.
|
|
107
108
|
function parse(argv: ReadonlyArray<string>): FileArguments | undefined {
|
|
108
|
-
|
|
109
|
-
const { values, positionals } = parseArgs({
|
|
110
|
-
args: [...argv],
|
|
111
|
-
options: OPTIONS,
|
|
112
|
-
allowPositionals: true,
|
|
113
|
-
})
|
|
109
|
+
const parsed = cli_flags.arguments_of(argv, OPTIONS)
|
|
114
110
|
|
|
115
|
-
|
|
116
|
-
} catch {
|
|
117
|
-
return undefined
|
|
118
|
-
}
|
|
111
|
+
return parsed === undefined ? undefined : arguments_of(parsed.values, parsed.positionals[0])
|
|
119
112
|
}
|
|
120
113
|
|
|
121
114
|
// A classification label is the body's to declare, so an extra `--label` naming one the body does not
|
|
@@ -129,6 +129,7 @@ async function run(argv: ReadonlyArray<string>): Promise<number> {
|
|
|
129
129
|
issue_report_failures.print_blocks(
|
|
130
130
|
reports.flatMap((report) => issue_blocks(report)),
|
|
131
131
|
BLOCK_SEPARATOR,
|
|
132
|
+
`issue:read ${issue_numbers.join(' ')}`,
|
|
132
133
|
)
|
|
133
134
|
|
|
134
135
|
return issue_report_failures.report_failures(reports, FAILURE_TERMS)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { capped_output } from '#scripts/document/capped-print'
|
|
2
|
+
|
|
1
3
|
// The failure report `issue:read` and `issue:state` share (joshuafolkken/kit#3046). Both read a batch
|
|
2
4
|
// of numbers, print a block for each one that answered, and name every one that did not; only the
|
|
3
5
|
// result kind that counts as an answer and what a gap must not be mistaken for differ between them.
|
|
@@ -22,10 +24,12 @@ interface FailureTerms<K extends string> {
|
|
|
22
24
|
misreading: string
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
// Under the Bash output cap, or as part files past it — a batch of long threads is the shape that
|
|
28
|
+
// overflowed (joshuafolkken/kit#3143).
|
|
29
|
+
function print_blocks(blocks: ReadonlyArray<string>, separator: string, label: string): void {
|
|
26
30
|
if (blocks.length === 0) return
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
capped_output.capped_print(blocks.join(separator), label)
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
// A number that resolves to nothing is an answer — a typo, or another repository's number quoted in
|
|
@@ -188,7 +188,7 @@ function print_states(reports: ReadonlyArray<IssueReport>): void {
|
|
|
188
188
|
const should_attribute = reports.length > 1
|
|
189
189
|
const blocks = reports.flatMap((report) => state_blocks(report, should_attribute))
|
|
190
190
|
|
|
191
|
-
issue_report_failures.print_blocks(blocks, BLOCK_SEPARATOR)
|
|
191
|
+
issue_report_failures.print_blocks(blocks, BLOCK_SEPARATOR, 'issue:state')
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
async function run(argv: ReadonlyArray<string>): Promise<number> {
|
|
@@ -52,13 +52,16 @@ function has_line(body: string, text: string): boolean {
|
|
|
52
52
|
return has_unfenced_line(body, text)
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
// The lines between `
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
function
|
|
55
|
+
// The lines between the first unfenced line `is_target` accepts and the next unfenced `## ` heading,
|
|
56
|
+
// trimmed of the heading line itself; an empty array when no line is accepted. A `## ` line inside a
|
|
57
|
+
// code fence is example text, so it neither opens nor closes a section (joshuafolkken/kit#3066).
|
|
58
|
+
function section_lines_matching(
|
|
59
|
+
body: string,
|
|
60
|
+
is_target: (line: string) => boolean,
|
|
61
|
+
): ReadonlyArray<string> {
|
|
59
62
|
const lines = body.split('\n')
|
|
60
63
|
const visible = unfenced_lines(body)
|
|
61
|
-
const start = visible.findIndex((line) =>
|
|
64
|
+
const start = visible.findIndex((line) => is_target(line))
|
|
62
65
|
|
|
63
66
|
if (start === -1) return []
|
|
64
67
|
|
|
@@ -68,6 +71,18 @@ function section_lines(body: string, heading: string): ReadonlyArray<string> {
|
|
|
68
71
|
return end === -1 ? rest : rest.slice(0, end)
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
// A heading matched inside a sentence is not the section heading, so only a line that is exactly the
|
|
75
|
+
// heading opens the section.
|
|
76
|
+
function section_lines(body: string, heading: string): ReadonlyArray<string> {
|
|
77
|
+
return section_lines_matching(body, (line) => is_the_heading(line, heading))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const markdown_section = {
|
|
81
|
+
section_lines,
|
|
82
|
+
section_lines_matching,
|
|
83
|
+
unfenced_lines,
|
|
84
|
+
has_line,
|
|
85
|
+
has_unfenced_line,
|
|
86
|
+
}
|
|
72
87
|
|
|
73
88
|
export { markdown_section }
|
|
@@ -1,26 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { execSync } from 'node:child_process'
|
|
3
|
-
import { readFile } from 'node:fs/promises'
|
|
4
3
|
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { git_gh_issue_read } from '#scripts/gh/git-gh-issue-read'
|
|
5
|
+
import { parse_json_object_safe } from '#scripts/git/parse-json-array'
|
|
5
6
|
import { error_text } from '#scripts/lib/error-message'
|
|
6
7
|
import { observation_ledger_home } from '#scripts/observations/observation-ledger-home'
|
|
8
|
+
import { z } from 'zod'
|
|
7
9
|
import { baseline_measure, type Baseline } from './baseline-measure'
|
|
8
10
|
|
|
9
|
-
// `josh measure:rerun <
|
|
11
|
+
// `josh measure:rerun <N>` — read a behavior-change Issue body after merge, re-run each baseline
|
|
10
12
|
// command, and print the before/after pair (joshuafolkken/kit#2212). A value that did not move means
|
|
11
13
|
// the premise the rule rested on is refuted, so a line is appended to the observation ledger — reusing
|
|
12
14
|
// that append-only, same-key mechanism rather than a second one.
|
|
15
|
+
//
|
|
16
|
+
// **The baseline is shell, and the body is written by whoever filed the Issue** (joshuafolkken/kit#3064).
|
|
17
|
+
// A baseline needs pipes and loops (`git log … | wc -l`, `for d in $(ls …)`), so an allow-list of
|
|
18
|
+
// commands cannot make it safe; what can is who wrote it. The input is therefore the Issue number, not
|
|
19
|
+
// a file, so the author's `author_association` arrives with the body from the same REST read, and only
|
|
20
|
+
// a body written by someone with write access to the repository is run.
|
|
13
21
|
|
|
14
22
|
const FAILURE_EXIT_CODE = 1
|
|
15
|
-
const
|
|
23
|
+
const ARGV_OFFSET = 2
|
|
24
|
+
const ISSUE_NUMBER_PATTERN = /^[1-9]\d*$/u
|
|
25
|
+
const ISSUE_FIELDS = 'body,author_association'
|
|
26
|
+
const TRUSTED_ASSOCIATIONS: ReadonlySet<string> = new Set(['OWNER', 'MEMBER', 'COLLABORATOR'])
|
|
27
|
+
const TRUSTED_LIST = [...TRUSTED_ASSOCIATIONS].join(' / ')
|
|
28
|
+
const UNKNOWN_ASSOCIATION = 'unknown'
|
|
29
|
+
const USAGE = 'Usage: josh measure:rerun <issue-number>'
|
|
16
30
|
const NO_BASELINE = 'no `command → value` baseline found in ## ベースライン — nothing to re-run'
|
|
17
31
|
const DATE_END = 10
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
//
|
|
32
|
+
// One baseline command's budget — the same minute `josh-harness.ts` gives one command it spawns. A
|
|
33
|
+
// baseline is a count or a digest that answers in seconds, so a command still running after a minute
|
|
34
|
+
// is stuck, and without a bound it would hold the whole re-measurement open with nothing printed.
|
|
35
|
+
const MEASURE_TIMEOUT_MS = 60_000
|
|
36
|
+
const issue_schema = z.object({
|
|
37
|
+
body: z.string().nullish(),
|
|
38
|
+
author_association: z.string().nullish(),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
type IssueFields = z.infer<typeof issue_schema>
|
|
42
|
+
type BodyRead = { kind: 'trusted'; body: string } | { kind: 'refused'; reason: string }
|
|
43
|
+
|
|
44
|
+
// The command's stdout, trimmed to one value. A failing or timed-out command yields its message so the
|
|
45
|
+
// pair still prints and the remaining baselines still run, rather than aborting the whole
|
|
46
|
+
// re-measurement.
|
|
21
47
|
function measure(command: string): string {
|
|
22
48
|
try {
|
|
23
|
-
return execSync(command, { encoding: 'utf8' }).trim()
|
|
49
|
+
return execSync(command, { encoding: 'utf8', timeout: MEASURE_TIMEOUT_MS }).trim()
|
|
24
50
|
} catch (error) {
|
|
25
51
|
return `(command failed: ${error_text.message_of(error)})`
|
|
26
52
|
}
|
|
@@ -56,8 +82,35 @@ function today(now: Date): string {
|
|
|
56
82
|
return now.toISOString().slice(0, DATE_END)
|
|
57
83
|
}
|
|
58
84
|
|
|
59
|
-
async function
|
|
60
|
-
const
|
|
85
|
+
async function read_issue(issue_number: string): Promise<IssueFields | undefined> {
|
|
86
|
+
const json = await git_gh_issue_read.issue_view_json(issue_number, ISSUE_FIELDS)
|
|
87
|
+
|
|
88
|
+
return json === undefined ? undefined : parse_json_object_safe(json, issue_schema)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// The body, only when its author can write to the repository. A read that fails is refused as well:
|
|
92
|
+
// an unknown author is not a trusted one.
|
|
93
|
+
async function read_trusted_body(issue_number: string): Promise<BodyRead> {
|
|
94
|
+
const issue = await read_issue(issue_number)
|
|
95
|
+
|
|
96
|
+
if (issue === undefined) {
|
|
97
|
+
return { kind: 'refused', reason: `could not read issue #${issue_number}` }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const association = issue.author_association ?? UNKNOWN_ASSOCIATION
|
|
101
|
+
|
|
102
|
+
if (!TRUSTED_ASSOCIATIONS.has(association)) {
|
|
103
|
+
return {
|
|
104
|
+
kind: 'refused',
|
|
105
|
+
reason: `refusing to run the baseline of #${issue_number}: its author is ${association}, not ${TRUSTED_LIST}`,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { kind: 'trusted', body: issue.body ?? '' }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function rerun(body: string, now: Date): Promise<number> {
|
|
113
|
+
const baselines = baseline_measure.parse_baselines(body)
|
|
61
114
|
|
|
62
115
|
if (baselines.length === 0) {
|
|
63
116
|
console.error(NO_BASELINE)
|
|
@@ -76,14 +129,22 @@ async function rerun(body_path: string, now: Date): Promise<number> {
|
|
|
76
129
|
return 0
|
|
77
130
|
}
|
|
78
131
|
|
|
79
|
-
async function run(
|
|
80
|
-
if (
|
|
132
|
+
async function run(issue_number: string | undefined, now: Date): Promise<number> {
|
|
133
|
+
if (issue_number === undefined || !ISSUE_NUMBER_PATTERN.test(issue_number)) {
|
|
81
134
|
console.error(USAGE)
|
|
82
135
|
|
|
83
136
|
return FAILURE_EXIT_CODE
|
|
84
137
|
}
|
|
85
138
|
|
|
86
|
-
|
|
139
|
+
const read = await read_trusted_body(issue_number)
|
|
140
|
+
|
|
141
|
+
if (read.kind === 'refused') {
|
|
142
|
+
console.error(read.reason)
|
|
143
|
+
|
|
144
|
+
return FAILURE_EXIT_CODE
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return await rerun(read.body, now)
|
|
87
148
|
}
|
|
88
149
|
|
|
89
150
|
async function main(argv: ReadonlyArray<string>): Promise<void> {
|
|
@@ -92,8 +153,6 @@ async function main(argv: ReadonlyArray<string>): Promise<void> {
|
|
|
92
153
|
|
|
93
154
|
const measure_rerun_cli = { run }
|
|
94
155
|
|
|
95
|
-
const ARGV_OFFSET = 2
|
|
96
|
-
|
|
97
156
|
if (process.argv[1] === fileURLToPath(import.meta.url)) await main(process.argv.slice(ARGV_OFFSET))
|
|
98
157
|
|
|
99
158
|
export { measure_rerun_cli }
|
|
@@ -59,7 +59,7 @@ const LANE_COMMANDS: Record<string, CommandEntry> = {
|
|
|
59
59
|
description:
|
|
60
60
|
'Block until any of the named in-flight lane children completes; prints which one finished',
|
|
61
61
|
category: 'AI tools',
|
|
62
|
-
reference: ['<issue> [<issue>...]', 'automation', ['processes']],
|
|
62
|
+
reference: ['<issue> [<issue>...] [--owner <pid>]', 'automation', ['files', 'processes']],
|
|
63
63
|
default_script_arguments: ['await'],
|
|
64
64
|
},
|
|
65
65
|
'lane:launch': {
|