@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
|
@@ -22,6 +22,13 @@ const PROJECT_COMMANDS: Record<string, CommandEntry> = {
|
|
|
22
22
|
['files', 'git', 'network', 'processes'],
|
|
23
23
|
],
|
|
24
24
|
},
|
|
25
|
+
'dogfood:commit': {
|
|
26
|
+
script: 'scripts/dogfood/dogfood-commit-cli.ts',
|
|
27
|
+
description: "Make the first commit of a dogfood run's own kit-test-* project",
|
|
28
|
+
category: 'Project',
|
|
29
|
+
reference: ['<dir>', 'automation', ['files', 'git']],
|
|
30
|
+
is_kit_only: true,
|
|
31
|
+
},
|
|
25
32
|
init: {
|
|
26
33
|
script: 'scripts/init/init.ts',
|
|
27
34
|
description: 'Initialize config in a new project',
|
|
@@ -40,7 +47,7 @@ const PROJECT_COMMANDS: Record<string, CommandEntry> = {
|
|
|
40
47
|
},
|
|
41
48
|
'registry:migrate': {
|
|
42
49
|
script: 'scripts/registry-migration/migrate.ts',
|
|
43
|
-
description: 'Migrate a
|
|
50
|
+
description: 'Migrate a project from GitHub Packages to public npm',
|
|
44
51
|
category: 'Project',
|
|
45
52
|
reference: ['', 'developer', ['files', 'network', 'processes']],
|
|
46
53
|
},
|
|
@@ -89,7 +89,7 @@ const WORKFLOW_COMMANDS: Record<string, CommandEntry> = {
|
|
|
89
89
|
script: 'scripts/issue/measure-rerun-cli.ts',
|
|
90
90
|
description: 'Re-run a merged issue’s baseline command and print the before/after pair',
|
|
91
91
|
category: 'Workflow',
|
|
92
|
-
reference: ['<
|
|
92
|
+
reference: ['<issue-number>', 'automation', ['network', 'processes', 'files']],
|
|
93
93
|
},
|
|
94
94
|
// A script rather than an `sh -c` chain, because it has a precondition to enforce: run inside a
|
|
95
95
|
// linked work tree it would hijack the default branch from every other one (joshuafolkken/kit#1535).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { repo_origin } from '#scripts/discovery/repo-origin'
|
|
4
|
-
import {
|
|
4
|
+
import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
|
|
5
5
|
|
|
6
6
|
const GITHUB_RUN_COMMANDS = new Set([
|
|
7
7
|
'run:entry',
|
|
@@ -19,11 +19,8 @@ function explanation(root: string, command: string): string | undefined {
|
|
|
19
19
|
return `${command} requires Git and a GitHub origin. Run git init and add a GitHub origin first.`
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
reject: false,
|
|
25
|
-
})
|
|
26
|
-
if (result.exitCode === 0 && repo_origin.parse_origin_url(result.stdout)) return undefined
|
|
22
|
+
const url = git_spawn_sync.origin_url(root)
|
|
23
|
+
if (url !== undefined && repo_origin.parse_origin_url(url)) return undefined
|
|
27
24
|
|
|
28
25
|
return `${command} requires a GitHub origin. Add an origin pointing to GitHub first.`
|
|
29
26
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process'
|
|
2
2
|
import { setTimeout as sleep } from 'node:timers/promises'
|
|
3
|
+
import { parseArgs } from 'node:util'
|
|
4
|
+
import { run_liveness } from '#scripts/run/run-liveness'
|
|
3
5
|
import { lane_child_invocation } from './lane-child-invocation'
|
|
4
6
|
import { lane_handoff } from './lane-handoff'
|
|
5
7
|
|
|
@@ -22,15 +24,30 @@ const MS_PER_SECOND = 1000
|
|
|
22
24
|
const ISSUE_PATTERN = /^[1-9]\d*$/u
|
|
23
25
|
const PROCESS_FOUND = 0
|
|
24
26
|
|
|
27
|
+
// `is_settled` is read once, when the wait starts: a child that ended between two `lane:await` calls
|
|
28
|
+
// never appears in the second one, and only its settled issue tells it apart from one not yet
|
|
29
|
+
// launched (joshuafolkken/kit#3133).
|
|
25
30
|
interface AwaitState {
|
|
26
31
|
appeared: boolean
|
|
27
32
|
disappeared_at: number | undefined
|
|
28
33
|
first_polled_at: number | undefined
|
|
34
|
+
is_settled?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface AwaitArguments {
|
|
38
|
+
issues: ReadonlyArray<string>
|
|
39
|
+
owner: number | undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface RawAwaitArguments {
|
|
43
|
+
positionals: Array<string>
|
|
44
|
+
values: { owner?: string | undefined }
|
|
29
45
|
}
|
|
30
46
|
|
|
31
47
|
// Injectable for testing -- the CLI never overrides these.
|
|
32
48
|
interface AwaitOptions {
|
|
33
49
|
is_running?: (issue: string) => boolean
|
|
50
|
+
is_settled?: (issue: string) => Promise<boolean>
|
|
34
51
|
poll_ms?: number
|
|
35
52
|
reconfirm_ms?: number
|
|
36
53
|
never_appeared_timeout_ms?: number
|
|
@@ -43,6 +60,7 @@ interface CheckConfig {
|
|
|
43
60
|
}
|
|
44
61
|
|
|
45
62
|
interface RunConfig extends CheckConfig {
|
|
63
|
+
is_settled: (issue: string) => Promise<boolean>
|
|
46
64
|
poll_ms: number
|
|
47
65
|
}
|
|
48
66
|
|
|
@@ -55,23 +73,75 @@ function is_process_running_default(issue: string): boolean {
|
|
|
55
73
|
return lane_handoff.is_ship_running(issue, process.cwd())
|
|
56
74
|
}
|
|
57
75
|
|
|
58
|
-
|
|
59
|
-
|
|
76
|
+
// The same positive evidence `run:liveness` answers `settled` on: the issue closed, or it was parked.
|
|
77
|
+
async function is_settled_default(issue: string): Promise<boolean> {
|
|
78
|
+
return (await run_liveness.read_child_settled(issue)) === true
|
|
60
79
|
}
|
|
61
80
|
|
|
62
|
-
|
|
63
|
-
|
|
81
|
+
// An unknown flag or a flag without its value is `undefined`, which is usage.
|
|
82
|
+
function read_await_args(rest: ReadonlyArray<string>): RawAwaitArguments | undefined {
|
|
83
|
+
try {
|
|
84
|
+
return parseArgs({
|
|
85
|
+
args: [...rest],
|
|
86
|
+
allowPositionals: true,
|
|
87
|
+
strict: true,
|
|
88
|
+
options: { owner: { type: 'string' } },
|
|
89
|
+
})
|
|
90
|
+
} catch {
|
|
91
|
+
return undefined
|
|
92
|
+
}
|
|
64
93
|
}
|
|
65
94
|
|
|
66
|
-
|
|
95
|
+
// Every positional is an issue number, at least one is given, and `--owner` is a pid.
|
|
96
|
+
function is_valid(raw: RawAwaitArguments): boolean {
|
|
97
|
+
const { owner } = raw.values
|
|
98
|
+
const numbers = owner === undefined ? raw.positionals : [...raw.positionals, owner]
|
|
99
|
+
|
|
100
|
+
return raw.positionals.length > 0 && numbers.every((value) => ISSUE_PATTERN.test(value))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// The issues to wait on and the waiting session's pid, or `undefined` for anything else.
|
|
104
|
+
function parse_arguments(rest: ReadonlyArray<string>): AwaitArguments | undefined {
|
|
105
|
+
const raw = read_await_args(rest)
|
|
106
|
+
|
|
107
|
+
if (raw === undefined || !is_valid(raw)) return undefined
|
|
108
|
+
|
|
109
|
+
const { owner } = raw.values
|
|
110
|
+
|
|
111
|
+
return { issues: raw.positionals, owner: owner === undefined ? undefined : Number(owner) }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function make_initial_state(is_settled: boolean): AwaitState {
|
|
115
|
+
return { appeared: false, disappeared_at: undefined, first_polled_at: undefined, is_settled }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function make_states(
|
|
119
|
+
issues: ReadonlyArray<string>,
|
|
120
|
+
is_settled: (issue: string) => Promise<boolean>,
|
|
121
|
+
): Promise<Map<string, AwaitState>> {
|
|
122
|
+
const settled = await Promise.all(issues.map(async (issue) => await is_settled(issue)))
|
|
123
|
+
|
|
124
|
+
return new Map(issues.map((issue, index) => [issue, make_initial_state(settled[index] === true)]))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function resolve_timing(
|
|
128
|
+
options: AwaitOptions,
|
|
129
|
+
): Pick<RunConfig, 'never_appeared_timeout_ms' | 'poll_ms' | 'reconfirm_ms'> {
|
|
67
130
|
return {
|
|
68
|
-
is_running: options.is_running ?? is_process_running_default,
|
|
69
131
|
poll_ms: options.poll_ms ?? DEFAULT_POLL_MS,
|
|
70
132
|
reconfirm_ms: options.reconfirm_ms ?? RECONFIRM_MS,
|
|
71
133
|
never_appeared_timeout_ms: options.never_appeared_timeout_ms ?? NEVER_APPEARED_TIMEOUT_MS,
|
|
72
134
|
}
|
|
73
135
|
}
|
|
74
136
|
|
|
137
|
+
function resolve_options(options: AwaitOptions): RunConfig {
|
|
138
|
+
return {
|
|
139
|
+
is_running: options.is_running ?? is_process_running_default,
|
|
140
|
+
is_settled: options.is_settled ?? is_settled_default,
|
|
141
|
+
...resolve_timing(options),
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
75
145
|
function update_liveness(state: AwaitState, is_live: boolean): void {
|
|
76
146
|
if (!is_live) return
|
|
77
147
|
|
|
@@ -79,13 +149,16 @@ function update_liveness(state: AwaitState, is_live: boolean): void {
|
|
|
79
149
|
state.disappeared_at = undefined
|
|
80
150
|
}
|
|
81
151
|
|
|
82
|
-
//
|
|
152
|
+
// Returns the issue when it had already settled before the wait started — it ended and will never
|
|
153
|
+
// appear. Otherwise throws if it never appeared within the timeout window, and records the first-seen time.
|
|
83
154
|
function check_not_appeared(
|
|
84
155
|
issue: string,
|
|
85
156
|
state: AwaitState,
|
|
86
157
|
now_ms: number,
|
|
87
158
|
timeout_ms: number,
|
|
88
|
-
):
|
|
159
|
+
): string | undefined {
|
|
160
|
+
if (state.is_settled === true) return issue
|
|
161
|
+
|
|
89
162
|
state.first_polled_at ??= now_ms
|
|
90
163
|
const elapsed_ms = now_ms - state.first_polled_at
|
|
91
164
|
|
|
@@ -94,10 +167,13 @@ function check_not_appeared(
|
|
|
94
167
|
`lane child ${issue} never appeared within ${String(timeout_ms / MS_PER_SECOND)}s`,
|
|
95
168
|
)
|
|
96
169
|
}
|
|
170
|
+
|
|
171
|
+
return undefined
|
|
97
172
|
}
|
|
98
173
|
|
|
99
174
|
// Returns the issue that has confirmed-completed this tick, `undefined` if none yet.
|
|
100
|
-
// The `appeared` guard prevents a process that never showed up from being declared done
|
|
175
|
+
// The `appeared` guard prevents a process that never showed up from being declared done, unless its
|
|
176
|
+
// issue had already settled when the wait started.
|
|
101
177
|
function check_issue(
|
|
102
178
|
issue: string,
|
|
103
179
|
state: AwaitState,
|
|
@@ -111,9 +187,7 @@ function check_issue(
|
|
|
111
187
|
if (is_live) return undefined
|
|
112
188
|
|
|
113
189
|
if (!state.appeared) {
|
|
114
|
-
check_not_appeared(issue, state, now_ms, config.never_appeared_timeout_ms)
|
|
115
|
-
|
|
116
|
-
return undefined
|
|
190
|
+
return check_not_appeared(issue, state, now_ms, config.never_appeared_timeout_ms)
|
|
117
191
|
}
|
|
118
192
|
|
|
119
193
|
if (state.disappeared_at === undefined) {
|
|
@@ -144,7 +218,7 @@ async function wait_for_any(
|
|
|
144
218
|
options: AwaitOptions = {},
|
|
145
219
|
): Promise<string> {
|
|
146
220
|
const config = resolve_options(options)
|
|
147
|
-
const states = make_states(issues)
|
|
221
|
+
const states = await make_states(issues, config.is_settled)
|
|
148
222
|
|
|
149
223
|
for (;;) {
|
|
150
224
|
const completed = check_any(states, Date.now(), config)
|
|
@@ -157,13 +231,13 @@ async function wait_for_any(
|
|
|
157
231
|
}
|
|
158
232
|
|
|
159
233
|
const lane_await = {
|
|
160
|
-
ISSUE_PATTERN,
|
|
161
234
|
NEVER_APPEARED_TIMEOUT_MS,
|
|
162
235
|
RECONFIRM_MS,
|
|
163
236
|
check_issue,
|
|
164
237
|
is_process_running_default,
|
|
238
|
+
parse_arguments,
|
|
165
239
|
wait_for_any,
|
|
166
240
|
}
|
|
167
241
|
|
|
168
|
-
export type { AwaitState, CheckConfig }
|
|
242
|
+
export type { AwaitArguments, AwaitState, CheckConfig }
|
|
169
243
|
export { lane_await }
|
package/scripts/lane/lane-cli.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { OpenIssueData } from '#scripts/git/schemas'
|
|
|
6
6
|
import { issue_cite, type IssueCiter } from '#scripts/issue/issue-cite'
|
|
7
7
|
import { error_text } from '#scripts/lib/error-message'
|
|
8
8
|
import { run_carry } from '#scripts/run/run-carry'
|
|
9
|
+
import { run_carry_reclaim } from '#scripts/run/run-carry-reclaim'
|
|
9
10
|
import { lane_await } from './lane-await'
|
|
10
11
|
import { lane_close, type CloseOutcome, type SweepOutcome } from './lane-close'
|
|
11
12
|
import { lane_dispatch, type DispatchOutcome } from './lane-dispatch'
|
|
@@ -40,7 +41,7 @@ const USAGE = [
|
|
|
40
41
|
' josh lane:prune',
|
|
41
42
|
' josh lane:output <issue-number> [<path>]',
|
|
42
43
|
' josh lane:dispatch <issue-number>',
|
|
43
|
-
' josh lane:await <issue-number> ...',
|
|
44
|
+
' josh lane:await <issue-number> ... [--owner <pid>]',
|
|
44
45
|
].join('\n')
|
|
45
46
|
|
|
46
47
|
type Handler = (rest: ReadonlyArray<string>) => Promise<number>
|
|
@@ -356,22 +357,19 @@ async function dispatch_command(rest: ReadonlyArray<string>): Promise<number> {
|
|
|
356
357
|
return await report_dispatch(await lane_dispatch.dispatch_child(issue), issue)
|
|
357
358
|
}
|
|
358
359
|
|
|
359
|
-
// Each valid argument is an issue number; any non-number stops parsing and triggers usage.
|
|
360
|
-
function parse_issues(rest: ReadonlyArray<string>): ReadonlyArray<string> | undefined {
|
|
361
|
-
if (rest.length === 0) return undefined
|
|
362
|
-
const issues = rest.filter((value) => lane_await.ISSUE_PATTERN.test(value))
|
|
363
|
-
|
|
364
|
-
return issues.length === rest.length ? issues : undefined
|
|
365
|
-
}
|
|
366
|
-
|
|
367
360
|
// The completed issue number goes to standard output so `N=$(pnpm josh lane:await ...)` captures
|
|
368
|
-
// which child finished without parsing stderr.
|
|
361
|
+
// which child finished without parsing stderr. `--owner` reclaims the carry record for a resumed
|
|
362
|
+
// conversation before the wait (`run-carry-reclaim.ts`).
|
|
369
363
|
async function await_command(rest: ReadonlyArray<string>): Promise<number> {
|
|
370
|
-
const
|
|
364
|
+
const parsed = lane_await.parse_arguments(rest)
|
|
371
365
|
|
|
372
|
-
if (
|
|
366
|
+
if (parsed === undefined) return report_usage()
|
|
367
|
+
|
|
368
|
+
if (parsed.owner !== undefined) {
|
|
369
|
+
await run_carry_reclaim.reclaim_carry(run_carry.owner_of(parsed.owner))
|
|
370
|
+
}
|
|
373
371
|
|
|
374
|
-
const completed = await lane_await.wait_for_any(issues)
|
|
372
|
+
const completed = await lane_await.wait_for_any(parsed.issues)
|
|
375
373
|
|
|
376
374
|
console.info(completed)
|
|
377
375
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, rmSync } from 'node:fs'
|
|
2
2
|
import { git_command } from '#scripts/git/git-command'
|
|
3
3
|
import { git_worktree } from '#scripts/git/git-worktree'
|
|
4
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
4
5
|
import { lane_paths } from './lane-paths'
|
|
5
6
|
import { lane_reap } from './lane-reap'
|
|
6
7
|
import { lane_registry, type LaneInfo } from './lane-registry'
|
|
@@ -147,7 +148,9 @@ async function try_close(issue: string): Promise<boolean> {
|
|
|
147
148
|
const outcome = await close_lane(issue)
|
|
148
149
|
|
|
149
150
|
return outcome.kind !== 'incomplete'
|
|
150
|
-
} catch {
|
|
151
|
+
} catch (error) {
|
|
152
|
+
error_text.trace_swallowed('lane_close.try_close', error)
|
|
153
|
+
|
|
151
154
|
return false
|
|
152
155
|
}
|
|
153
156
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
-
import { parseArgs } from 'node:util'
|
|
4
3
|
import { josh_command } from '#scripts/josh/josh-run'
|
|
4
|
+
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
5
5
|
import { INSTALL_TIMEOUT_MS } from '#scripts/lib/timeouts'
|
|
6
6
|
import { execa } from 'execa'
|
|
7
7
|
|
|
@@ -35,21 +35,15 @@ interface LaunchContext {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function read_context(argv: ReadonlyArray<string>): LaunchContext | undefined {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (issue === undefined || rest.length > 0 || !ISSUE_PATTERN.test(issue)) return undefined
|
|
48
|
-
|
|
49
|
-
return { issue, stash: parsed.values.stash }
|
|
50
|
-
} catch {
|
|
51
|
-
return undefined
|
|
52
|
-
}
|
|
38
|
+
const parsed = cli_flags.arguments_of(argv, OPTIONS)
|
|
39
|
+
|
|
40
|
+
if (parsed === undefined) return undefined
|
|
41
|
+
|
|
42
|
+
const [issue, ...rest] = parsed.positionals
|
|
43
|
+
|
|
44
|
+
if (issue === undefined || rest.length > 0 || !ISSUE_PATTERN.test(issue)) return undefined
|
|
45
|
+
|
|
46
|
+
return { issue, stash: parsed.values.stash }
|
|
53
47
|
}
|
|
54
48
|
|
|
55
49
|
// The pop brought in the `pnpm-lock.yaml` `josh latest` rewrote, so this one lane installs a second
|
|
@@ -6,6 +6,7 @@ import { agent_argv } from '#scripts/agent/agent-argv'
|
|
|
6
6
|
import { agent_role_profile } from '#scripts/agent/agent-role-profile'
|
|
7
7
|
import { git_common_directory } from '#scripts/git/git-common-directory'
|
|
8
8
|
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
9
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
9
10
|
import { detached_launch } from '#scripts/run/detached-launch'
|
|
10
11
|
import { run_ship_review } from '#scripts/run/run-ship-review'
|
|
11
12
|
import { z } from 'zod'
|
|
@@ -102,7 +103,9 @@ function write_response(target: string, body: z.infer<typeof response_schema>):
|
|
|
102
103
|
async function launched(lane: LaneInfo, round: ReviewRound): Promise<boolean> {
|
|
103
104
|
try {
|
|
104
105
|
return await launch_review(lane, round)
|
|
105
|
-
} catch {
|
|
106
|
+
} catch (error) {
|
|
107
|
+
error_text.trace_swallowed('openai_review_broker.launched', error)
|
|
108
|
+
|
|
106
109
|
return false
|
|
107
110
|
}
|
|
108
111
|
}
|
package/scripts/lib/cli-flags.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseArgs, type ParseArgsConfig } from 'node:util'
|
|
1
|
+
import { parseArgs, type ParseArgsConfig, type ParseArgsOptionsConfig } from 'node:util'
|
|
2
2
|
|
|
3
3
|
// Argument reading for the commands under `scripts/`.
|
|
4
4
|
//
|
|
@@ -32,6 +32,35 @@ function parse_or_undefined<T extends ParseArgsConfig>(
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
// The two shapes every command reads `argv` in: flags alone, or flags beside positionals. Both are
|
|
36
|
+
// strict, so an unknown flag is `undefined` rather than a silently ignored typo; the commands had each
|
|
37
|
+
// carried a one-line wrapper spelling these out (joshuafolkken/kit#3072).
|
|
38
|
+
type FlagsOnly<T extends ParseArgsOptionsConfig> = ReturnType<
|
|
39
|
+
typeof parseArgs<{ args: Array<string>; options: T }>
|
|
40
|
+
>
|
|
41
|
+
type WithPositionals<T extends ParseArgsOptionsConfig> = ReturnType<
|
|
42
|
+
typeof parseArgs<{ args: Array<string>; options: T; allowPositionals: true }>
|
|
43
|
+
>
|
|
44
|
+
|
|
45
|
+
function values_of<T extends ParseArgsOptionsConfig>(
|
|
46
|
+
argv: ReadonlyArray<string>,
|
|
47
|
+
options: T,
|
|
48
|
+
): FlagsOnly<T>['values'] | undefined {
|
|
49
|
+
return parse_or_undefined({ args: [...argv], options })?.values
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function arguments_of<T extends ParseArgsOptionsConfig>(
|
|
53
|
+
argv: ReadonlyArray<string>,
|
|
54
|
+
options: T,
|
|
55
|
+
): WithPositionals<T> | undefined {
|
|
56
|
+
return parse_or_undefined({ args: [...argv], options, allowPositionals: true })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// A string option's value, or `undefined` where the flag was absent or parsed as anything else.
|
|
60
|
+
function string_of(value: unknown): string | undefined {
|
|
61
|
+
return typeof value === 'string' ? value : undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const cli_flags = { refuse_unknown_flags, parse_or_undefined, values_of, arguments_of, string_of }
|
|
36
65
|
|
|
37
66
|
export { cli_flags }
|
|
@@ -4,6 +4,17 @@ function message_of(error: unknown): string {
|
|
|
4
4
|
return error instanceof Error ? error.message : String(error)
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const DEBUG_ENV_KEY = 'JOSH_DEBUG'
|
|
8
|
+
|
|
9
|
+
// A catch that folds a failure into a state verdict — "dirty", "not merged", "not written" — leaves no
|
|
10
|
+
// trace of why by default, so a wrong verdict cannot be followed back to its cause (joshuafolkken/kit#3067).
|
|
11
|
+
// `JOSH_DEBUG` set to any non-blank value writes the swallowed reason to stderr; unset, nothing changes.
|
|
12
|
+
function trace_swallowed(where: string, error: unknown): void {
|
|
13
|
+
if ((process.env[DEBUG_ENV_KEY] ?? '').trim() === '') return
|
|
14
|
+
|
|
15
|
+
process.stderr.write(`josh debug: ${where}: ${message_of(error)}\n`)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const error_text = { message_of, trace_swallowed }
|
|
8
19
|
|
|
9
20
|
export { error_text }
|
|
@@ -40,10 +40,6 @@ const LS_FILES_FLAGS: ReadonlyArray<string> = [
|
|
|
40
40
|
'-z',
|
|
41
41
|
]
|
|
42
42
|
const NUL = '\0'
|
|
43
|
-
// `--show-toplevel` prints the absolute repository (or worktree) root from anywhere inside it, so the
|
|
44
|
-
// root-relative names `--full-name` returns resolve to the right absolute paths wherever the command
|
|
45
|
-
// was invoked.
|
|
46
|
-
const SHOW_TOPLEVEL: ReadonlyArray<string> = ['rev-parse', '--show-toplevel']
|
|
47
43
|
|
|
48
44
|
function is_lint_target(relative_path: string): boolean {
|
|
49
45
|
return LINT_EXTENSIONS.has(path.extname(relative_path))
|
|
@@ -53,12 +49,6 @@ function split_names(output: string): ReadonlyArray<string> {
|
|
|
53
49
|
return output.split(NUL).filter((name) => name.length > 0)
|
|
54
50
|
}
|
|
55
51
|
|
|
56
|
-
// `git_spawn.read` is the one place ordinary git commands are spawned from, so this reuses it rather
|
|
57
|
-
// than growing a second spawn site (joshuafolkken/kit#1640).
|
|
58
|
-
async function repo_root(): Promise<string> {
|
|
59
|
-
return await git_spawn.read([...SHOW_TOPLEVEL])
|
|
60
|
-
}
|
|
61
|
-
|
|
62
52
|
// The names resolve against the repository `root`, never the process cwd: `--full-name` prints them
|
|
63
53
|
// relative to the root wherever the command runs, so resolving against cwd would double the prefix
|
|
64
54
|
// and drop every file when the command is invoked from a subdirectory.
|
|
@@ -73,7 +63,6 @@ async function lint_target_files(root: string): Promise<ReadonlyArray<string>> {
|
|
|
73
63
|
const line_targets = {
|
|
74
64
|
is_lint_target,
|
|
75
65
|
lint_target_files,
|
|
76
|
-
repo_root,
|
|
77
66
|
split_names,
|
|
78
67
|
}
|
|
79
68
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { git_command } from '#scripts/git/git-command'
|
|
4
5
|
import { line_budget, type FileBudget, type LineBudget } from './line-budget'
|
|
5
6
|
import { line_targets } from './line-targets'
|
|
6
7
|
|
|
@@ -122,10 +123,11 @@ function render(budgets: ReadonlyArray<FileBudget>, project_root: string): strin
|
|
|
122
123
|
return [header(budgets), ...rows_for(budgets, project_root)].join('\n')
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
// The scan resolves the repository root first — `budgets_for` and the row display both key off it
|
|
126
|
-
//
|
|
126
|
+
// The scan resolves the repository root first — `budgets_for` and the row display both key off it, and
|
|
127
|
+
// `git ls-files --full-name` names files relative to it — so a run started from a subdirectory reports
|
|
128
|
+
// the same files as one started from the root.
|
|
127
129
|
async function run_scan(): Promise<number> {
|
|
128
|
-
const root = await
|
|
130
|
+
const root = await git_command.repository_root()
|
|
129
131
|
const budgets = await scan_budgets(root)
|
|
130
132
|
|
|
131
133
|
process.stdout.write(`${render(budgets, root)}\n`)
|
|
@@ -2,7 +2,7 @@ import { existsSync } from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { git_location_environment } from '#scripts/git/git-location-environment'
|
|
4
4
|
import { ls_remote_branch_arguments } from '#scripts/git/git-ls-remote'
|
|
5
|
-
import {
|
|
5
|
+
import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
|
|
6
6
|
|
|
7
7
|
// The git probes propagation needs before it writes anything into a working tree.
|
|
8
8
|
//
|
|
@@ -16,7 +16,6 @@ const GIT_TIMEOUT_MS = 10_000
|
|
|
16
16
|
// `rev-parse` and tight for an ssh handshake, and a probe that times out answers "no" — which for
|
|
17
17
|
// the two below would report a push that never reached origin as a push nobody attempted.
|
|
18
18
|
const REMOTE_TIMEOUT_MS = 60_000
|
|
19
|
-
const SUCCESS_EXIT_CODE = 0
|
|
20
19
|
const DEFAULT_BRANCH_REF = 'refs/remotes/origin/HEAD'
|
|
21
20
|
const FALLBACK_DEFAULT_BRANCH = 'main'
|
|
22
21
|
const ORIGIN_PREFIX = 'refs/remotes/origin/'
|
|
@@ -54,15 +53,11 @@ function run_git(
|
|
|
54
53
|
args: ReadonlyArray<string>,
|
|
55
54
|
timeout_ms: number = GIT_TIMEOUT_MS,
|
|
56
55
|
): string | undefined {
|
|
57
|
-
|
|
56
|
+
return git_spawn_sync.read(args, {
|
|
58
57
|
cwd: repository_path,
|
|
59
|
-
reject: false,
|
|
60
58
|
timeout: timeout_ms,
|
|
61
|
-
env: {
|
|
62
|
-
extendEnv: true,
|
|
59
|
+
env: { ...git_spawn_sync.C_LOCALE, ...git_location_environment.location_free_environment() },
|
|
63
60
|
})
|
|
64
|
-
|
|
65
|
-
return result.exitCode === SUCCESS_EXIT_CODE ? result.stdout.trim() : undefined
|
|
66
61
|
}
|
|
67
62
|
|
|
68
63
|
// The repository's default branch, from the remote's own HEAD. Falls back to `main` when the ref is
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { git_command } from '#scripts/git/git-command'
|
|
1
2
|
import { line_targets } from '#scripts/lines/line-targets'
|
|
2
3
|
import { import_graph } from './import-graph'
|
|
3
4
|
import { refactor_lint, type CategoryResult } from './refactor-lint'
|
|
@@ -97,7 +98,7 @@ async function scan(root: string): Promise<ScanResult> {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
async function run_scan(): Promise<number> {
|
|
100
|
-
const root = await
|
|
101
|
+
const root = await git_command.repository_root()
|
|
101
102
|
const result = await scan(root)
|
|
102
103
|
|
|
103
104
|
process.stdout.write(`${render_result(result)}\n`)
|
|
@@ -99,25 +99,33 @@ function is_github_tarball_line(line: string): boolean {
|
|
|
99
99
|
return line.trimStart().startsWith('tarball: https://npm.pkg.github.com/')
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
// Integrities are keyed by `name@version`, so two scoped packages sharing a version never collide.
|
|
103
|
+
function package_id(key: string): string {
|
|
104
|
+
const name = fix_gh_packages_logic.package_path_from_key(key)
|
|
105
|
+
|
|
106
|
+
return `${name}@${fix_gh_packages_logic.package_version_from_key(key)}`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function clean_scoped_line(
|
|
103
110
|
line: string,
|
|
104
|
-
|
|
111
|
+
id: string,
|
|
105
112
|
integrities: ReadonlyMap<string, IntegrityPair>,
|
|
106
113
|
): string | undefined {
|
|
107
|
-
if (!
|
|
114
|
+
if (!id) return line
|
|
108
115
|
if (is_github_tarball_line(line)) return undefined
|
|
109
116
|
const cleaned = remove_flow_tarball(line)
|
|
110
|
-
const pair = integrities.get(
|
|
117
|
+
const pair = integrities.get(id)
|
|
111
118
|
if (pair === undefined) return cleaned
|
|
112
119
|
|
|
113
120
|
return cleaned.split(`integrity: ${pair[0]}`).join(`integrity: ${pair[1]}`)
|
|
114
121
|
}
|
|
115
122
|
|
|
116
|
-
function
|
|
117
|
-
if (!is_package_entry(line)) return
|
|
118
|
-
const prefix =
|
|
123
|
+
function next_scoped_id(line: string, id: string): string {
|
|
124
|
+
if (!is_package_entry(line)) return id
|
|
125
|
+
const prefix = ` '${SCOPE}/`
|
|
126
|
+
if (!line.startsWith(prefix)) return ''
|
|
119
127
|
|
|
120
|
-
return
|
|
128
|
+
return package_id(line.slice(" '".length).split("'", 1)[0] ?? '')
|
|
121
129
|
}
|
|
122
130
|
|
|
123
131
|
function integrity_pair(
|
|
@@ -135,12 +143,12 @@ function lock_integrities(
|
|
|
135
143
|
): Map<string, IntegrityPair> {
|
|
136
144
|
const packages = fix_gh_packages_logic.parse_lockfile_packages(lockfile)
|
|
137
145
|
const pairs = new Map<string, IntegrityPair>()
|
|
138
|
-
const
|
|
146
|
+
const scoped_entries = Object.entries(packages).filter(([key]) => key.startsWith(`${SCOPE}/`))
|
|
139
147
|
|
|
140
|
-
for (const [key, entry] of
|
|
141
|
-
const
|
|
142
|
-
const pair = integrity_pair(entry.resolution?.integrity, npm_integrities.get(
|
|
143
|
-
if (pair !== undefined) pairs.set(
|
|
148
|
+
for (const [key, entry] of scoped_entries) {
|
|
149
|
+
const id = package_id(key)
|
|
150
|
+
const pair = integrity_pair(entry.resolution?.integrity, npm_integrities.get(id))
|
|
151
|
+
if (pair !== undefined) pairs.set(id, pair)
|
|
144
152
|
}
|
|
145
153
|
|
|
146
154
|
return pairs
|
|
@@ -150,17 +158,17 @@ function push_if_defined(lines: Array<string>, cleaned: string | undefined): voi
|
|
|
150
158
|
if (cleaned !== undefined) lines.push(cleaned)
|
|
151
159
|
}
|
|
152
160
|
|
|
153
|
-
function
|
|
161
|
+
function rewrite_scoped_lockfile(
|
|
154
162
|
lockfile: string,
|
|
155
163
|
npm_integrities: ReadonlyMap<string, string>,
|
|
156
164
|
): string {
|
|
157
|
-
let
|
|
165
|
+
let id = ''
|
|
158
166
|
const lines: Array<string> = []
|
|
159
167
|
const integrities = lock_integrities(lockfile, npm_integrities)
|
|
160
168
|
|
|
161
169
|
for (const line of lockfile.split('\n')) {
|
|
162
|
-
|
|
163
|
-
const cleaned =
|
|
170
|
+
id = next_scoped_id(line, id)
|
|
171
|
+
const cleaned = clean_scoped_line(line, id, integrities)
|
|
164
172
|
|
|
165
173
|
push_if_defined(lines, cleaned)
|
|
166
174
|
}
|
|
@@ -206,7 +214,8 @@ function previous_registry(project_npmrc: string, user_npmrc: string): string {
|
|
|
206
214
|
|
|
207
215
|
function plan(project_npmrc: string, user_npmrc: string, lockfile: string): MigrationPlan {
|
|
208
216
|
const packages = scoped_packages(lockfile)
|
|
209
|
-
|
|
217
|
+
// Every scoped package may migrate; `migrate` blocks any whose locked version is not on public npm.
|
|
218
|
+
const blocked: Array<string> = []
|
|
210
219
|
if (has_duplicate_mapping(project_npmrc, user_npmrc)) blocked.push('duplicate registry mappings')
|
|
211
220
|
const unsupported = unsupported_registry(project_npmrc)
|
|
212
221
|
if (unsupported !== undefined) blocked.push(unsupported)
|
|
@@ -220,7 +229,7 @@ const migrate_logic = {
|
|
|
220
229
|
plan,
|
|
221
230
|
github_tarballs,
|
|
222
231
|
is_github_tarball,
|
|
223
|
-
|
|
232
|
+
rewrite_scoped_lockfile,
|
|
224
233
|
scoped_versions,
|
|
225
234
|
}
|
|
226
235
|
|