@joshuafolkken/kit 1.1387.0 → 1.1453.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/epic-commands/SKILL.md +1 -1
- package/.claude/skills/epic-commands/epic-bundle.md +5 -0
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +5 -3
- package/.claude/skills/workflow-commands/backlogrun-park.md +2 -0
- package/.claude/skills/workflow-commands/backlogrun-steps.md +13 -10
- package/.claude/skills/workflow-commands/backlogrun.md +3 -2
- package/.claude/skills/workflow-commands/chain-rule.md +4 -2
- package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
- package/.claude/skills/workflow-commands/halfrun.md +1 -1
- package/.claude/skills/workflow-commands/issue-comments.md +6 -0
- package/.claude/skills/workflow-commands/kickoff.md +12 -13
- package/.claude/skills/workflow-commands/observation-filing.md +1 -1
- package/.claude/skills/workflow-commands/observation-ledger.md +3 -2
- package/.claude/skills/workflow-commands/pre-gate-cut.md +5 -3
- package/.claude/skills/workflow-commands/prerequisite.md +6 -0
- package/.claude/skills/workflow-commands/retrospective.md +5 -5
- package/.claude/skills/workflow-commands/split-assessment.md +2 -2
- package/README.md +27 -4
- package/dist/hooks/audit-provision.js +2 -2
- package/dist/hooks/batch-guard.js +7 -7
- package/dist/hooks/{chunk-2YSW3CXH.js → chunk-3C4XRYG6.js} +35 -11
- package/dist/hooks/{chunk-GJ7JPIHN.js → chunk-5C7L6FRC.js} +2704 -190
- package/dist/hooks/{chunk-FZ6FUSBM.js → chunk-723HKZO5.js} +1 -1
- package/dist/hooks/{chunk-STDSEPWU.js → chunk-D3NJXN5M.js} +4 -4
- package/dist/hooks/{chunk-NEC4AT4Z.js → chunk-DGT54IH5.js} +4 -0
- package/dist/hooks/{chunk-LMWBRJIP.js → chunk-EF5GQ6WS.js} +3 -3
- package/dist/hooks/{chunk-JBBMAPC4.js → chunk-IX6TTTI6.js} +4 -0
- package/dist/hooks/{chunk-IYVOLN6A.js → chunk-LBFYEOXJ.js} +3 -3
- package/dist/hooks/{chunk-SL4GQ2XP.js → chunk-MBJYQPEK.js} +106 -23
- package/dist/hooks/{chunk-WNFJLBXI.js → chunk-NO4AALP5.js} +3 -3
- package/dist/hooks/{chunk-J3KSS7II.js → chunk-OLMO72ON.js} +1 -1
- package/dist/hooks/{chunk-DCY2GWHC.js → chunk-SHYUQOIS.js} +5 -5
- package/dist/hooks/{chunk-EAQEGME7.js → chunk-UT7YC3N2.js} +1 -1
- package/dist/hooks/{chunk-EBSHN25W.js → chunk-W5ZKGTSY.js} +4 -4
- package/dist/hooks/{chunk-GNDJ7NJ7.js → chunk-Y3GKIKGW.js} +209 -56
- package/dist/hooks/{chunk-U32VSRNG.js → chunk-Z4XI7CZS.js} +89 -34
- package/dist/hooks/codex-hook-adapter.js +15 -15
- package/dist/hooks/duplicate-read-guard.js +8 -8
- package/dist/hooks/format-edited.js +5 -5
- package/dist/hooks/inputs.json +261 -201
- package/dist/hooks/investigation-guard.js +8 -8
- package/dist/hooks/pretool-guard.js +14 -14
- package/dist/hooks/session-lang.js +69 -16
- package/dist/hooks/stop-guard.js +13 -38
- package/dist/josh.js +95 -19
- package/package.json +4 -3
- package/prompts/collaboration-workflow/issue-template.md +10 -8
- package/prompts/review.md +30 -4
- package/scripts/agent/claude-agent-argv.ts +13 -1
- package/scripts/auth/auth-setup-cli.ts +101 -0
- package/scripts/auth/claude-token-output.ts +78 -0
- package/scripts/auth/claude-token.ts +68 -0
- package/scripts/auto-ok/auto-ok-judge-cli.ts +125 -0
- package/scripts/auto-ok/auto-ok-judge-read.ts +95 -0
- package/scripts/auto-ok/auto-ok-judge-write.ts +109 -0
- package/scripts/auto-ok/auto-ok-judge.ts +67 -0
- package/scripts/backlog/backlog-drive-cli.ts +14 -0
- package/scripts/backlog/backlog-drive-epic.ts +24 -8
- package/scripts/backlog/backlog-drive-launch.ts +3 -0
- package/scripts/backlog/backlog-drive-named-offer.ts +7 -2
- package/scripts/backlog/backlog-drive-named.ts +4 -2
- package/scripts/backlog/backlog-drive-unread.ts +56 -0
- package/scripts/backlog/backlog-drive.ts +38 -11
- package/scripts/backlog/backlog-overlap.ts +22 -5
- package/scripts/backlog/backlog-ready.ts +1 -3
- package/scripts/backlog/backlogrun-args.ts +13 -2
- package/scripts/backlog/backlogrun-cli.ts +91 -19
- package/scripts/backlog/backlogrun-launch.ts +24 -0
- package/scripts/backlog/backlogrun-replace.ts +166 -0
- package/scripts/backlog/backlogrun-stop.ts +178 -0
- package/scripts/behavior/behavior-cli.ts +6 -1
- package/scripts/doctor/doctor-io.ts +60 -9
- package/scripts/doctor/doctor.ts +20 -1
- package/scripts/document/document-byte-budget.ts +3 -3
- package/scripts/epic/epic-bundle-cli.ts +8 -8
- package/scripts/epic/epic-bundle-referenced.ts +2 -5
- package/scripts/epic/epic-bundle.ts +39 -4
- package/scripts/exports/unused-members-cli.ts +2 -1
- package/scripts/followup/followup-checks-event.ts +82 -0
- package/scripts/followup/git-followup-finish.ts +16 -2
- package/scripts/followup/git-followup-workflow.ts +5 -2
- package/scripts/followup/git-pr-followup-checks.ts +18 -4
- package/scripts/followup/git-pr-followup-wrapup.ts +5 -0
- package/scripts/followup/git-pr-followup.ts +4 -1
- package/scripts/fresh-machine/fresh-machine-cli.ts +204 -0
- package/scripts/fresh-machine/fresh-machine-docker.ts +65 -0
- package/scripts/fresh-machine/fresh-machine-image.ts +62 -0
- package/scripts/fresh-machine/fresh-machine-log.ts +61 -0
- package/scripts/fresh-machine/fresh-machine-patterns.ts +175 -0
- package/scripts/fresh-machine/fresh-machine-run.ts +215 -0
- package/scripts/fresh-machine/fresh-machine-script.ts +45 -0
- package/scripts/fresh-machine/fresh-machine-spec.ts +57 -0
- package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +34 -0
- package/scripts/fresh-machine/specs/kit-test-backlogrun.json +31 -0
- package/scripts/gate/gate-report.ts +70 -15
- package/scripts/gate/project-checks.ts +18 -4
- package/scripts/gate/scoped-green.ts +2 -1
- package/scripts/gate/verification-gate.ts +4 -3
- package/scripts/gh/gh-author-association.ts +14 -0
- package/scripts/gh/git-gh-conditional.ts +66 -0
- package/scripts/gh/git-gh-exec.ts +1 -1
- package/scripts/gh/git-gh-issue-read.ts +6 -3
- package/scripts/gh/git-gh-issue-rest.ts +1 -0
- package/scripts/gh/git-gh-issue-write.ts +5 -1
- package/scripts/gh/git-gh-issue.ts +13 -1
- package/scripts/gh/git-pr-checks-eval.ts +13 -0
- package/scripts/gh/git-pr-checks-parse.ts +8 -1
- package/scripts/gh/git-pr-checks-watch.ts +11 -4
- package/scripts/gh/git-pr-checks.ts +36 -2
- package/scripts/git/git-command.ts +16 -4
- package/scripts/git/git-schemas.ts +1 -0
- package/scripts/git/git-spawn-sync.ts +1 -1
- package/scripts/hooks/pretool-guard.ts +3 -1
- package/scripts/init/hook-command-rewrite.ts +31 -0
- package/scripts/init/init-actions.ts +7 -4
- package/scripts/init/init-ai-copy.ts +34 -11
- package/scripts/init/init-basic.ts +9 -4
- package/scripts/init/init-bootstrap.ts +6 -2
- package/scripts/init/init-dry-run-report.ts +150 -0
- package/scripts/init/init-dry-run.ts +189 -0
- package/scripts/init/init-install.ts +8 -1
- package/scripts/init/init-logic-toolchain.ts +76 -0
- package/scripts/init/init-logic.ts +2 -0
- package/scripts/init/init.ts +62 -42
- package/scripts/init/kit-setup-state.ts +11 -17
- package/scripts/init/own-formatter.ts +21 -0
- package/scripts/init/project-profile.ts +60 -12
- package/scripts/init/start-dry-run.ts +70 -0
- package/scripts/init/start-exec.ts +27 -1
- package/scripts/init/start-plan.ts +23 -3
- package/scripts/init/start-setup-pr.ts +13 -24
- package/scripts/init/start-steps.ts +62 -1
- package/scripts/init/start.ts +38 -17
- package/scripts/init/workflow-mark.ts +35 -0
- package/scripts/init/working-tree.ts +116 -0
- package/scripts/issue/issue-ack-cli.ts +84 -0
- package/scripts/issue/issue-auto-ok.ts +68 -99
- package/scripts/issue/issue-file-cli.ts +17 -17
- package/scripts/issue/issue-file.ts +1 -6
- package/scripts/issue/issue-labels.ts +46 -10
- package/scripts/issue/issue-release-cli.ts +69 -23
- package/scripts/issue/issue-release.ts +3 -1
- package/scripts/issue/issue-state-cli.ts +14 -4
- package/scripts/issue/issue-watch-diff.ts +152 -0
- package/scripts/issue/issue-watch-gate.ts +81 -0
- package/scripts/issue/issue-watch-hook.ts +54 -0
- package/scripts/issue/issue-watch.ts +240 -0
- package/scripts/issue/measure-rerun-cli.ts +3 -4
- package/scripts/josh/josh-command-map.ts +4 -0
- package/scripts/josh/josh-commands-ai.ts +11 -0
- package/scripts/josh/josh-commands-backlog.ts +2 -2
- package/scripts/josh/josh-commands-development.ts +1 -1
- package/scripts/josh/josh-commands-issue.ts +10 -3
- package/scripts/josh/josh-commands-lane.ts +11 -2
- package/scripts/josh/josh-commands-project.ts +26 -2
- package/scripts/josh/josh-verdict.ts +33 -4
- package/scripts/josh/process-identity.ts +32 -6
- package/scripts/lane/claude-lane-broker.ts +142 -0
- package/scripts/lane/lane-await.ts +6 -2
- package/scripts/lane/lane-broker-server.ts +69 -0
- package/scripts/lane/lane-cache.ts +37 -7
- package/scripts/lane/lane-capacity.ts +8 -6
- package/scripts/lane/lane-child-invocation.ts +1 -0
- package/scripts/lane/lane-cli.ts +15 -1
- package/scripts/lane/lane-close.ts +15 -6
- package/scripts/lane/lane-dispatch.ts +36 -10
- package/scripts/lane/lane-environment.ts +1 -1
- package/scripts/lane/lane-launch-cli.ts +13 -4
- package/scripts/lane/lane-ledger.ts +21 -3
- package/scripts/lane/lane-measure-cli.ts +10 -2
- package/scripts/lane/lane-open.ts +9 -1
- package/scripts/lane/lane-preserve.ts +49 -0
- package/scripts/lane/lane-reap.ts +20 -5
- package/scripts/lane/lane-registry.ts +6 -0
- package/scripts/lane/lane-restart-cli.ts +51 -0
- package/scripts/lane/lane-restart.ts +175 -0
- package/scripts/lane/lane-session-broker.ts +363 -0
- package/scripts/lane/lane-spent.ts +195 -0
- package/scripts/lane/lane-stage-stats.ts +27 -3
- package/scripts/lane/lane-stop-stats.ts +96 -0
- package/scripts/lane/openai-lane-supervisor.ts +2 -2
- package/scripts/lane/process-tools.ts +31 -0
- package/scripts/lib/buffered-process.ts +36 -2
- package/scripts/lib/pnpm-reach.ts +27 -0
- package/scripts/lib/status-icons.ts +5 -1
- package/scripts/lint/lint-parallel.ts +19 -2
- package/scripts/observations/observation-ledger-home.ts +1 -0
- package/scripts/observations/observation-ledger-migrate.ts +3 -15
- package/scripts/release/release-issue-close.ts +133 -0
- package/scripts/release/release-publish.ts +12 -1
- package/scripts/repo/repository-labels.ts +11 -1
- package/scripts/review/comment-ranges.ts +199 -0
- package/scripts/review/review-attest-cli.ts +21 -6
- package/scripts/review/review-brief-cli.ts +24 -7
- package/scripts/review/review-code-tree.ts +119 -0
- package/scripts/review/review-comment-round.ts +114 -0
- package/scripts/review/review-record-cli.ts +39 -17
- package/scripts/review/review-round2-cli.ts +16 -5
- package/scripts/review/review-round2.ts +30 -10
- package/scripts/review/review-stamps.ts +22 -1
- package/scripts/rules/implementation-cut.ts +3 -3
- package/scripts/rules/pre-gate-cut.ts +1 -1
- package/scripts/run/board/run-board-checks.ts +42 -0
- package/scripts/run/board/run-board-cli.ts +2 -0
- package/scripts/run/board/run-board-every.ts +3 -1
- package/scripts/run/board/run-board-fit.ts +16 -5
- package/scripts/run/board/run-board-fresh.ts +64 -0
- package/scripts/run/board/run-board-header.ts +129 -53
- package/scripts/run/board/run-board-labels.ts +47 -4
- package/scripts/run/board/run-board-lane-event.ts +66 -0
- package/scripts/run/board/run-board-layout.ts +57 -9
- package/scripts/run/board/run-board-now.ts +234 -0
- package/scripts/run/board/run-board-operation-read.ts +75 -0
- package/scripts/run/board/run-board-operation.ts +150 -0
- package/scripts/run/board/run-board-phase.ts +26 -1
- package/scripts/run/board/run-board-plan.ts +4 -6
- package/scripts/run/board/run-board-read.ts +49 -3
- package/scripts/run/board/run-board-reason.ts +124 -0
- package/scripts/run/board/run-board-render-checks.ts +55 -0
- package/scripts/run/board/run-board-render-legend.ts +12 -3
- package/scripts/run/board/run-board-render-slot.ts +2 -2
- package/scripts/run/board/run-board-render-tail.ts +128 -0
- package/scripts/run/board/run-board-render-thanks.ts +50 -0
- package/scripts/run/board/run-board-render-title.ts +58 -0
- package/scripts/run/board/run-board-render.ts +96 -80
- package/scripts/run/board/run-board-row-yield.ts +32 -0
- package/scripts/run/board/run-board-setup.ts +61 -13
- package/scripts/run/board/run-board-state.ts +10 -0
- package/scripts/run/board/run-board-status.ts +66 -7
- package/scripts/run/board/run-board-thanks-csv.ts +71 -0
- package/scripts/run/board/run-board-thanks-read.ts +112 -0
- package/scripts/run/board/run-board-thanks.ts +53 -0
- package/scripts/run/board/run-board-tick.ts +50 -21
- package/scripts/run/board/run-board-usual.ts +44 -0
- package/scripts/run/carry/run-carry-args.ts +9 -1
- package/scripts/run/carry/run-carry-cli.ts +2 -45
- package/scripts/run/carry/run-carry-end.ts +73 -0
- package/scripts/run/carry/run-carry.ts +8 -1
- package/scripts/run/entry/run-entry-cli.ts +5 -0
- package/scripts/run/event/run-event-checks.ts +54 -0
- package/scripts/run/event/run-event-render.ts +2 -0
- package/scripts/run/event/run-event-scope.ts +1 -0
- package/scripts/run/event/run-event-setup.ts +55 -11
- package/scripts/run/event/run-event-stream.ts +27 -11
- package/scripts/run/merge/run-merge-cli.ts +7 -4
- package/scripts/run/merge/run-merge-ship.ts +61 -0
- package/scripts/run/merge/run-merge-steps.ts +7 -14
- package/scripts/run/merge/run-merge-token.ts +16 -2
- package/scripts/run/run-headless.ts +1 -0
- package/scripts/run/run-step.ts +2 -0
- package/scripts/run/ship/run-ship-cli.ts +22 -2
- package/scripts/run/ship/run-ship-return.ts +31 -13
- package/scripts/run/ship/run-ship-review-steps.ts +17 -4
- package/scripts/run/ship/run-ship-stage.ts +1 -0
- package/scripts/run/wake/run-wake-await.ts +130 -0
- package/scripts/run/wake/run-wake-cli.ts +60 -71
- package/scripts/run/wake/run-wake-describe.ts +34 -2
- package/scripts/run/wake/run-wake-driver.ts +11 -4
- package/scripts/run/wake/run-wake-session.ts +20 -3
- package/scripts/run/wake/run-wake-stop.ts +37 -0
- package/scripts/run/wake/run-wake-supervise.ts +44 -0
- package/scripts/sync/claude-settings-merge.ts +144 -0
- package/scripts/sync/sync-ai-files.ts +28 -3
- package/scripts/sync/sync.ts +15 -0
- package/scripts/test/project-test-script.ts +73 -0
- package/scripts/test/skip-marker.ts +14 -1
- package/scripts/test/test-related.ts +3 -2
- package/scripts/test/test-unit-guard.ts +78 -14
- package/scripts/time-runtime/time-reported-failure.ts +11 -3
- package/scripts/uninstall/distributed-files.ts +92 -0
- package/scripts/uninstall/uninstall-plan.ts +119 -0
- package/scripts/uninstall/uninstall-report.ts +61 -0
- package/scripts/uninstall/uninstall.ts +206 -0
- package/templates/AGENTS.basic.md +4 -0
- package/templates/GEMINI.basic.md +6 -0
- package/dist/hooks/chunk-6V5OPIUU.js +0 -13
- package/scripts/lane/openai-review-broker.ts +0 -262
- package/scripts/run/board/run-board-track.ts +0 -22
|
@@ -166,14 +166,25 @@ async function delete_branch(branch_name: string): Promise<void> {
|
|
|
166
166
|
// report "nothing is stranded" about a branch nobody measured, which is the one wrong answer that
|
|
167
167
|
// loses work. `count_merges` guards the same way and falls back to `0` instead, because there a
|
|
168
168
|
// missing count means an empty range rather than a claim about somebody's commits.
|
|
169
|
-
async function
|
|
170
|
-
const
|
|
171
|
-
const output = await git_spawn.read(['rev-list', '--count', range])
|
|
169
|
+
async function read_commit_count(revisions: ReadonlyArray<string>): Promise<number> {
|
|
170
|
+
const output = await git_spawn.read(['rev-list', '--count', ...revisions])
|
|
172
171
|
const parsed = Number(output)
|
|
173
172
|
|
|
174
173
|
if (output.length > 0 && Number.isFinite(parsed)) return parsed
|
|
175
174
|
|
|
176
|
-
throw new Error(
|
|
175
|
+
throw new Error(
|
|
176
|
+
`\`git rev-list --count ${revisions.join(' ')}\` printed \`${output}\` rather than a count`,
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function commit_count_beyond(base: string, tip: string): Promise<number> {
|
|
181
|
+
return await read_commit_count([`${base}..${tip}`])
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// How many commits `branch_name` holds that no remote-tracking ref reaches — what deleting the branch
|
|
185
|
+
// would lose. An unparseable count throws for the reason `commit_count_beyond`'s does.
|
|
186
|
+
async function commit_count_unpushed(branch_name: string): Promise<number> {
|
|
187
|
+
return await read_commit_count([branch_name, '--not', '--remotes'])
|
|
177
188
|
}
|
|
178
189
|
|
|
179
190
|
function is_exit_code_128(cause: unknown): boolean {
|
|
@@ -397,6 +408,7 @@ const git_command = {
|
|
|
397
408
|
checkout,
|
|
398
409
|
commit,
|
|
399
410
|
commit_count_beyond,
|
|
411
|
+
commit_count_unpushed,
|
|
400
412
|
delete_branch,
|
|
401
413
|
push,
|
|
402
414
|
pull_fast_forward,
|
|
@@ -72,4 +72,4 @@ function toplevel(timeout: number = GIT_TIMEOUT_MS): GitSyncResult {
|
|
|
72
72
|
const git_spawn_sync = { GIT_TIMEOUT_MS, C_LOCALE, run, read, origin_url, toplevel }
|
|
73
73
|
|
|
74
74
|
export { git_spawn_sync }
|
|
75
|
-
export type { GitSyncResult }
|
|
75
|
+
export type { GitSyncOptions, GitSyncResult }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { duplicate_read_outcome } from '#scripts/delegation/duplicate-read-guard'
|
|
2
2
|
import { investigation_refusal } from '#scripts/delegation/investigation-guard'
|
|
3
|
+
import { issue_watch_hook } from '#scripts/issue/issue-watch-hook'
|
|
3
4
|
import { hook_decision, type GuardOutcome } from '#scripts/josh/hook-decision'
|
|
4
5
|
import { lane_phase } from '#scripts/lane/lane-phase'
|
|
5
6
|
import { json_value } from '#scripts/lib/json-value'
|
|
@@ -104,8 +105,9 @@ async function mark_phase(outcome: GuardOutcome): Promise<void> {
|
|
|
104
105
|
// surfaced first.
|
|
105
106
|
async function async_reason(raw_payload: string): Promise<string | undefined> {
|
|
106
107
|
const watcher = await run_watcher_hook.watcher_hook_reason(raw_payload)
|
|
108
|
+
const cut = watcher ?? (await run_parent_cut_hook.parent_cut_reason(raw_payload))
|
|
107
109
|
|
|
108
|
-
return
|
|
110
|
+
return cut ?? (await issue_watch_hook.watch_reason())
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
async function pretool_outcome_async(raw_payload: string): Promise<GuardOutcome> {
|
|
@@ -25,6 +25,30 @@ const CONSUMER_PACKAGE_MANIFEST = `${claude_plugin_config.MARKETPLACE_PATH}/pack
|
|
|
25
25
|
const MISSING_INSTALL_NOTICE = 'kit hooks inactive: run pnpm install, then reread CLAUDE.md'
|
|
26
26
|
const MISSING_INSTALL_GUARD = `if [ ! -f ${CONSUMER_PACKAGE_MANIFEST} ]; then echo '${MISSING_INSTALL_NOTICE}' >&2; exit 0; fi; `
|
|
27
27
|
const CODEX_HOOKS_DESTINATION = '.codex/hooks.json'
|
|
28
|
+
// Every shape a hook command kit wrote can carry: the launcher as kit's own file names it, the
|
|
29
|
+
// launcher rebased onto the installed package, and the installed dispatcher the releases from
|
|
30
|
+
// 2026-09-13 (joshuafolkken/kit#1930) to the launcher rebased `pnpm josh` onto. The settings merge
|
|
31
|
+
// tells kit's hooks from a project's own by these and the list below, so the shapes stay beside the
|
|
32
|
+
// rewrite that produces them.
|
|
33
|
+
const KIT_COMMAND_MARKERS: ReadonlyArray<string> = [
|
|
34
|
+
RUN_HOOK_INVOCATION,
|
|
35
|
+
CONSUMER_RUN_HOOK_INVOCATION,
|
|
36
|
+
BUNDLE_INVOCATION,
|
|
37
|
+
]
|
|
38
|
+
// Before joshuafolkken/kit#1930 nothing was rebased: a consumer received these six as the whole hook
|
|
39
|
+
// command. A plain `pnpm josh <command>` is also how a project writes a hook of its own, so only the
|
|
40
|
+
// exact commands count — and kit still delivers each of them through the launcher, so one left in
|
|
41
|
+
// place would run its guard or formatter a second time. The list is history and never grows.
|
|
42
|
+
const PLAIN_KIT_COMMANDS: ReadonlySet<string> = new Set(
|
|
43
|
+
[
|
|
44
|
+
'audit:provision',
|
|
45
|
+
'batch:guard',
|
|
46
|
+
'format:edited',
|
|
47
|
+
'investigation:guard',
|
|
48
|
+
'rule:guard',
|
|
49
|
+
'session:lang',
|
|
50
|
+
].map((command) => `${PNPM_JOSH_PREFIX}${command}`),
|
|
51
|
+
)
|
|
28
52
|
// The values below are matched inside raw JSON text, so the root prefix is taken in its escaped form.
|
|
29
53
|
const PROJECT_ROOT_PREFIX = JSON.stringify(hook_launch.PROJECT_ROOT_PREFIX).slice(1, -1)
|
|
30
54
|
// Capture the value of every `"command"` field, escapes and all, so the rewrites below touch command
|
|
@@ -80,8 +104,15 @@ function apply_hook_command_rewrite_for_destination(
|
|
|
80
104
|
return content
|
|
81
105
|
}
|
|
82
106
|
|
|
107
|
+
function is_kit_hook_command(command: string): boolean {
|
|
108
|
+
if (PLAIN_KIT_COMMANDS.has(command)) return true
|
|
109
|
+
|
|
110
|
+
return KIT_COMMAND_MARKERS.some((marker) => command.includes(marker))
|
|
111
|
+
}
|
|
112
|
+
|
|
83
113
|
const hook_command_rewrite = {
|
|
84
114
|
apply_hook_command_rewrite_for_destination,
|
|
115
|
+
is_kit_hook_command,
|
|
85
116
|
rewrite_hook_commands,
|
|
86
117
|
}
|
|
87
118
|
|
|
@@ -106,7 +106,7 @@ function build_basic_settings_action(): FileAction {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
function build_basic_vscode_actions(shape: ProjectShape): ReadonlyArray<FileAction> {
|
|
109
|
-
const recommendations = shape.
|
|
109
|
+
const recommendations = shape.should_use_prettier
|
|
110
110
|
? [...BASIC_EXTENSIONS, PRETTIER_EXTENSION]
|
|
111
111
|
: BASIC_EXTENSIONS
|
|
112
112
|
const actions: Array<FileAction> = [
|
|
@@ -117,7 +117,7 @@ function build_basic_vscode_actions(shape: ProjectShape): ReadonlyArray<FileActi
|
|
|
117
117
|
),
|
|
118
118
|
]
|
|
119
119
|
|
|
120
|
-
if (shape.
|
|
120
|
+
if (shape.should_use_prettier) actions.push(build_basic_settings_action())
|
|
121
121
|
|
|
122
122
|
return actions
|
|
123
123
|
}
|
|
@@ -210,7 +210,7 @@ function build_basic_actions(shape: ProjectShape): ReadonlyArray<FileAction> {
|
|
|
210
210
|
if (shape.has_git) actions.push(build_gitignore_action())
|
|
211
211
|
if (shape.has_typescript) actions.push(build_basic_tsconfig_action())
|
|
212
212
|
|
|
213
|
-
if (shape.
|
|
213
|
+
if (shape.should_use_prettier) {
|
|
214
214
|
actions.push({
|
|
215
215
|
dest: BASIC_PRETTIER_CONFIG,
|
|
216
216
|
create: () =>
|
|
@@ -221,6 +221,9 @@ function build_basic_actions(shape: ProjectShape): ReadonlyArray<FileAction> {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
actions.push(...build_basic_vscode_actions(shape))
|
|
224
|
+
// The `.claude/settings.json` a `josh start` project receives wires the `session:lang` hook, whose
|
|
225
|
+
// unset default is `ja`, so the language is seeded from the locale as on `full`.
|
|
226
|
+
if (shape.has_workflow) actions.push(...session_language_environment.build_session_lang_actions())
|
|
224
227
|
|
|
225
228
|
return actions
|
|
226
229
|
}
|
|
@@ -248,4 +251,4 @@ function build_file_actions(shape?: ProjectShape): ReadonlyArray<FileAction> {
|
|
|
248
251
|
const init_actions = { build_file_actions, read_package_json }
|
|
249
252
|
|
|
250
253
|
export type { FileAction }
|
|
251
|
-
export { init_actions, PRETTIER_CONFIG_JS, BASIC_PRETTIER_CONFIG }
|
|
254
|
+
export { init_actions, PRETTIER_CONFIG_JS, BASIC_PRETTIER_CONFIG, LEFTHOOK_PATH }
|
|
@@ -25,9 +25,11 @@ const PRETTIER_IGNORE = '.prettierignore'
|
|
|
25
25
|
const GIT_ATTRIBUTES = '.gitattributes'
|
|
26
26
|
const CODE_OF_CONDUCT = 'CODE_OF_CONDUCT.md'
|
|
27
27
|
const { SECURITY_MD } = security_policy
|
|
28
|
+
const AGENTS_MD = 'AGENTS.md'
|
|
29
|
+
const GEMINI_MD = 'GEMINI.md'
|
|
28
30
|
const BASIC_AI_FILES = [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
AGENTS_MD,
|
|
32
|
+
GEMINI_MD,
|
|
31
33
|
PRETTIER_IGNORE,
|
|
32
34
|
GIT_ATTRIBUTES,
|
|
33
35
|
CODE_OF_CONDUCT,
|
|
@@ -36,6 +38,8 @@ const BASIC_AI_FILES = [
|
|
|
36
38
|
]
|
|
37
39
|
// A basic project gets its own template for these, not kit's development copy.
|
|
38
40
|
const BASIC_SOURCES: Readonly<Record<string, string>> = {
|
|
41
|
+
[AGENTS_MD]: 'templates/AGENTS.basic.md',
|
|
42
|
+
[GEMINI_MD]: 'templates/GEMINI.basic.md',
|
|
39
43
|
[PRETTIER_IGNORE]: 'templates/prettierignore.basic',
|
|
40
44
|
[WORKSPACE_YAML]: 'templates/pnpm-workspace.basic.yaml',
|
|
41
45
|
}
|
|
@@ -43,6 +47,9 @@ const BASIC_SOURCES: Readonly<Record<string, string>> = {
|
|
|
43
47
|
const CONSUMER_SOURCES: Readonly<Record<string, string>> = {
|
|
44
48
|
[SECURITY_MD]: security_policy.SECURITY_TEMPLATE,
|
|
45
49
|
}
|
|
50
|
+
// A basic project `josh start` set up for the GitHub Issue workflow runs its agents unattended, on the
|
|
51
|
+
// permissions, hooks and plugin this file declares (#3796).
|
|
52
|
+
const BASIC_WORKFLOW_AI_FILES = [...BASIC_AI_FILES, '.claude/settings.json']
|
|
46
53
|
const GITHUB_FILES = new Set([CODE_OF_CONDUCT, SECURITY_MD])
|
|
47
54
|
const BASIC_POINTER_MAPPING = { src: 'templates/cursorrules.basic', dest: '.cursorrules' }
|
|
48
55
|
const PULL_REQUEST_TEMPLATE = '.github/pull_request_template.md'
|
|
@@ -62,11 +69,15 @@ function did_read_unstamped(destination_path: string): boolean {
|
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
// What kit writes at a destination it copies to — the content `josh uninstall` compares a project's
|
|
73
|
+
// file against, so it is read from the one place the copy itself reads it.
|
|
74
|
+
function copied_content(source_path: string, destination_path: string): string {
|
|
75
|
+
return transform_copied_content(destination_path, readFileSync(source_path, 'utf8'))
|
|
76
|
+
}
|
|
67
77
|
|
|
78
|
+
function copy_ai_file(source_path: string, destination_path: string): void {
|
|
68
79
|
mkdirSync(path.dirname(destination_path), { recursive: true })
|
|
69
|
-
writeFileSync(destination_path,
|
|
80
|
+
writeFileSync(destination_path, copied_content(source_path, destination_path))
|
|
70
81
|
}
|
|
71
82
|
|
|
72
83
|
// `init` declines to modify a file that already exists, and that includes not stamping it: the
|
|
@@ -132,6 +143,14 @@ function merge_existing_workspace_yaml(source_path: string, destination_path: st
|
|
|
132
143
|
console.info(` ✔ updated ${WORKSPACE_YAML}`)
|
|
133
144
|
}
|
|
134
145
|
|
|
146
|
+
// The workspace file kit writes where the project has none.
|
|
147
|
+
function workspace_content(source_path: string, destination_path: string): string {
|
|
148
|
+
return transform_copied_content(
|
|
149
|
+
destination_path,
|
|
150
|
+
read_workspace_template(source_path, destination_path),
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
135
154
|
function did_skip_workspace_yaml_copy(source_path: string, destination_path: string): boolean {
|
|
136
155
|
if (existsSync(destination_path)) {
|
|
137
156
|
merge_existing_workspace_yaml(source_path, destination_path)
|
|
@@ -139,9 +158,7 @@ function did_skip_workspace_yaml_copy(source_path: string, destination_path: str
|
|
|
139
158
|
return false
|
|
140
159
|
}
|
|
141
160
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
writeFileSync(destination_path, transform_copied_content(destination_path, template))
|
|
161
|
+
writeFileSync(destination_path, workspace_content(source_path, destination_path))
|
|
145
162
|
console.info(` ✔ created ${WORKSPACE_YAML}`)
|
|
146
163
|
|
|
147
164
|
return false
|
|
@@ -331,7 +348,7 @@ function should_copy_github_file(filename: string, shape?: ProjectShape): boolea
|
|
|
331
348
|
function should_copy_basic_file(filename: string, shape?: ProjectShape): boolean {
|
|
332
349
|
if (filename !== PRETTIER_IGNORE || shape?.profile !== 'basic') return true
|
|
333
350
|
|
|
334
|
-
return shape.
|
|
351
|
+
return shape.should_use_prettier
|
|
335
352
|
}
|
|
336
353
|
|
|
337
354
|
function should_copy_ai_file(filename: string, shape?: ProjectShape): boolean {
|
|
@@ -342,8 +359,12 @@ function should_copy_ai_file(filename: string, shape?: ProjectShape): boolean {
|
|
|
342
359
|
)
|
|
343
360
|
}
|
|
344
361
|
|
|
362
|
+
function basic_ai_files(shape: ProjectShape): ReadonlyArray<string> {
|
|
363
|
+
return shape.has_workflow ? BASIC_WORKFLOW_AI_FILES : BASIC_AI_FILES
|
|
364
|
+
}
|
|
365
|
+
|
|
345
366
|
function ai_files(shape?: ProjectShape): ReadonlyArray<string> {
|
|
346
|
-
const files = shape?.profile === 'basic' ?
|
|
367
|
+
const files = shape?.profile === 'basic' ? basic_ai_files(shape) : init_logic.get_ai_copy_files()
|
|
347
368
|
|
|
348
369
|
return files.filter((filename) => should_copy_ai_file(filename, shape))
|
|
349
370
|
}
|
|
@@ -397,6 +418,8 @@ function run_ai_copies(shape?: ProjectShape): string | undefined {
|
|
|
397
418
|
|
|
398
419
|
const init_ai_copy = {
|
|
399
420
|
copy_ai_file,
|
|
421
|
+
copied_content,
|
|
422
|
+
workspace_content,
|
|
400
423
|
run_ai_copies,
|
|
401
424
|
ai_files,
|
|
402
425
|
ai_file_source,
|
|
@@ -405,4 +428,4 @@ const init_ai_copy = {
|
|
|
405
428
|
read_workspace_template,
|
|
406
429
|
}
|
|
407
430
|
|
|
408
|
-
export { init_ai_copy, CLAUDE_MD_FILENAME }
|
|
431
|
+
export { init_ai_copy, CLAUDE_MD_FILENAME, WORKSPACE_YAML }
|
|
@@ -3,6 +3,7 @@ import { json_value } from '#scripts/lib/json-value'
|
|
|
3
3
|
import { z } from 'zod'
|
|
4
4
|
import { init_logic } from './init-logic'
|
|
5
5
|
import type { ProjectShape } from './project-profile'
|
|
6
|
+
import { workflow_mark } from './workflow-mark'
|
|
6
7
|
|
|
7
8
|
const KIT_PACKAGE_NAME = '@joshuafolkken/kit'
|
|
8
9
|
const record_schema = z.record(z.string(), z.unknown())
|
|
@@ -16,13 +17,17 @@ function initial_manifest(): string {
|
|
|
16
17
|
return json_format.format_json({ private: true })
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
function with_recorded_profile(
|
|
20
|
+
function with_recorded_profile(
|
|
21
|
+
content: string,
|
|
22
|
+
profile: ProjectShape['profile'],
|
|
23
|
+
has_workflow = false,
|
|
24
|
+
): string {
|
|
20
25
|
const parsed: unknown = JSON.parse(content)
|
|
21
26
|
const manifest = record_schema.parse(parsed)
|
|
22
27
|
const existing = manifest['josh']
|
|
23
28
|
const josh = json_value.is_record(existing) ? existing : {}
|
|
24
29
|
|
|
25
|
-
manifest['josh'] = { ...josh, profile }
|
|
30
|
+
manifest['josh'] = { ...josh, profile, ...workflow_mark.recorded_fields(has_workflow) }
|
|
26
31
|
|
|
27
32
|
return json_format.format_json(manifest)
|
|
28
33
|
}
|
|
@@ -32,7 +37,7 @@ function merge_basic_manifest(
|
|
|
32
37
|
shape: ProjectShape,
|
|
33
38
|
versions: BasicVersions,
|
|
34
39
|
): string {
|
|
35
|
-
const with_profile = with_recorded_profile(content, shape.profile)
|
|
40
|
+
const with_profile = with_recorded_profile(content, shape.profile, shape.has_workflow)
|
|
36
41
|
const with_script = init_logic.merge_package_scripts(with_profile, {
|
|
37
42
|
preinstall: init_logic.SAFE_CHAIN_CMD,
|
|
38
43
|
josh: 'josh',
|
|
@@ -40,7 +45,7 @@ function merge_basic_manifest(
|
|
|
40
45
|
const with_kit = init_logic.merge_development_dependencies(with_script, {
|
|
41
46
|
[KIT_PACKAGE_NAME]: versions.kit,
|
|
42
47
|
})
|
|
43
|
-
const with_prettier = shape.
|
|
48
|
+
const with_prettier = shape.should_use_prettier
|
|
44
49
|
? init_logic.merge_development_dependencies(with_kit, { prettier: versions.prettier })
|
|
45
50
|
: with_kit
|
|
46
51
|
|
|
@@ -82,13 +82,17 @@ function ensure_project_manifest(project_root: string): void {
|
|
|
82
82
|
if (!existsSync(manifest_path)) writeFileSync(manifest_path, init_basic.initial_manifest())
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
function is_handed_off(): boolean {
|
|
86
|
+
return process.env[HANDOFF_ENV] !== undefined
|
|
87
|
+
}
|
|
88
|
+
|
|
85
89
|
// Returns the failure to report, or undefined once the project-installed kit finished the setup.
|
|
86
90
|
function hand_off(
|
|
87
91
|
package_directory: string,
|
|
88
92
|
project_root: string,
|
|
89
93
|
args: ReadonlyArray<string>,
|
|
90
94
|
): string | undefined {
|
|
91
|
-
if (
|
|
95
|
+
if (is_handed_off()) return REPEATED_HANDOFF
|
|
92
96
|
console.info('\n🚀 Installing @joshuafolkken/kit into the project, then running its josh init')
|
|
93
97
|
const template = readFileSync(path.join(package_directory, KIT_BUILDS_TEMPLATE), 'utf8')
|
|
94
98
|
const install = kit_install_step(has_kit_dependency(project_root), template)
|
|
@@ -100,5 +104,5 @@ function hand_off(
|
|
|
100
104
|
return undefined
|
|
101
105
|
}
|
|
102
106
|
|
|
103
|
-
const init_bootstrap = { is_project_kit, kit_build_flags, hand_off }
|
|
107
|
+
const init_bootstrap = { HANDOFF_ENV, is_project_kit, kit_build_flags, hand_off, is_handed_off }
|
|
104
108
|
export { init_bootstrap }
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { json_value } from '#scripts/lib/json-value'
|
|
2
|
+
|
|
3
|
+
type ChangeKind = 'create' | 'merge' | 'overwrite' | 'delete'
|
|
4
|
+
|
|
5
|
+
interface FileChange {
|
|
6
|
+
path: string
|
|
7
|
+
before: string | undefined
|
|
8
|
+
after: string | undefined
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type JsonRecord = Record<string, unknown>
|
|
12
|
+
|
|
13
|
+
const PACKAGE_JSON = 'package.json'
|
|
14
|
+
const JSON_EXTENSION = '.json'
|
|
15
|
+
const KIND_WIDTH = 'overwrite'.length
|
|
16
|
+
const NO_CHANGES = ' (no file changes)'
|
|
17
|
+
|
|
18
|
+
// Every path whose content differs between the two snapshots, sorted, so a run reads the same twice.
|
|
19
|
+
function changed_files(
|
|
20
|
+
before: ReadonlyMap<string, string>,
|
|
21
|
+
after: ReadonlyMap<string, string>,
|
|
22
|
+
): Array<FileChange> {
|
|
23
|
+
const paths = [...new Set([...before.keys(), ...after.keys()])].toSorted((left, right) =>
|
|
24
|
+
left.localeCompare(right),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
return paths
|
|
28
|
+
.filter((file) => before.get(file) !== after.get(file))
|
|
29
|
+
.map((file) => ({ path: file, before: before.get(file), after: after.get(file) }))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// A changed file is a merge when every line it had survives — the project's own content is kept —
|
|
33
|
+
// and an overwrite when any of its lines is replaced or removed.
|
|
34
|
+
function keeps_every_line(before: string, after: string): boolean {
|
|
35
|
+
const remaining = new Set(after.split('\n'))
|
|
36
|
+
|
|
37
|
+
return before.split('\n').every((line) => remaining.has(line))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function is_kept_array(before: ReadonlyArray<unknown>, after: unknown): boolean {
|
|
41
|
+
if (!Array.isArray(after)) return false
|
|
42
|
+
const remaining = new Set(after.map((value) => JSON.stringify(value)))
|
|
43
|
+
|
|
44
|
+
return before.every((value) => remaining.has(JSON.stringify(value)))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// A JSON file is rewritten whole when kit adds to it — `package.json` written out again, a list grown — so
|
|
48
|
+
// it is compared by value: a merge when every value it had is still there, wherever the lines moved.
|
|
49
|
+
function is_kept(before: unknown, after: unknown): boolean {
|
|
50
|
+
if (Array.isArray(before)) return is_kept_array(before, after)
|
|
51
|
+
if (!json_value.is_record(before) || !json_value.is_record(after)) return before === after
|
|
52
|
+
|
|
53
|
+
return Object.keys(before).every((key) => is_kept(before[key], after[key]))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Undefined when either side is not plain JSON (a `tsconfig.json` with comments), which falls back
|
|
57
|
+
// to the line test.
|
|
58
|
+
function keeps_every_value(before: string, after: string): boolean | undefined {
|
|
59
|
+
const old_value = json_value.parse_or_undefined(before)
|
|
60
|
+
const new_value = json_value.parse_or_undefined(after)
|
|
61
|
+
if (old_value === undefined || new_value === undefined) return undefined
|
|
62
|
+
|
|
63
|
+
return is_kept(old_value, new_value)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function keeps_content(file: string, before: string, after: string): boolean {
|
|
67
|
+
const is_kept_by_value = file.endsWith(JSON_EXTENSION)
|
|
68
|
+
? keeps_every_value(before, after)
|
|
69
|
+
: undefined
|
|
70
|
+
|
|
71
|
+
return is_kept_by_value ?? keeps_every_line(before, after)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function change_kind(change: FileChange): ChangeKind {
|
|
75
|
+
if (change.before === undefined) return 'create'
|
|
76
|
+
if (change.after === undefined) return 'delete'
|
|
77
|
+
|
|
78
|
+
return keeps_content(change.path, change.before, change.after) ? 'merge' : 'overwrite'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parse_record(content: string | undefined): JsonRecord {
|
|
82
|
+
const parsed = content === undefined ? undefined : json_value.parse_or_undefined(content)
|
|
83
|
+
|
|
84
|
+
return json_value.is_record(parsed) ? parsed : {}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function changed_keys(before: JsonRecord, after: JsonRecord): Array<string> {
|
|
88
|
+
const keys = [...new Set([...Object.keys(before), ...Object.keys(after)])]
|
|
89
|
+
|
|
90
|
+
return keys.filter((key) => JSON.stringify(before[key]) !== JSON.stringify(after[key]))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function entry_line(name: string, before: unknown, after: unknown): string {
|
|
94
|
+
if (before === undefined) return `+ ${name} ${JSON.stringify(after)}`
|
|
95
|
+
if (after === undefined) return `- ${name}`
|
|
96
|
+
|
|
97
|
+
return `~ ${name} ${JSON.stringify(before)} → ${JSON.stringify(after)}`
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// A missing field reads as an empty object, so a block added or removed whole is listed too.
|
|
101
|
+
function as_entries(value: unknown): JsonRecord | undefined {
|
|
102
|
+
if (value === undefined) return {}
|
|
103
|
+
|
|
104
|
+
return json_value.is_record(value) ? value : undefined
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// A field that is an object (`scripts`, `devDependencies`) is listed entry by entry, so the plan
|
|
108
|
+
// names each script and dependency rather than the whole block.
|
|
109
|
+
function field_lines(key: string, before: unknown, after: unknown): Array<string> {
|
|
110
|
+
const old_entries = as_entries(before)
|
|
111
|
+
const new_entries = as_entries(after)
|
|
112
|
+
|
|
113
|
+
if (old_entries === undefined || new_entries === undefined) {
|
|
114
|
+
return [entry_line(key, before, after)]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return changed_keys(old_entries, new_entries).map((name) =>
|
|
118
|
+
entry_line(`${key}.${name}`, old_entries[name], new_entries[name]),
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function package_json_lines(change: FileChange): Array<string> {
|
|
123
|
+
const before = parse_record(change.before)
|
|
124
|
+
const after = parse_record(change.after)
|
|
125
|
+
|
|
126
|
+
return changed_keys(before, after).flatMap((key) => field_lines(key, before[key], after[key]))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function change_lines(change: FileChange): Array<string> {
|
|
130
|
+
const head = ` ${change_kind(change).padEnd(KIND_WIDTH)} ${change.path}`
|
|
131
|
+
if (change.path !== PACKAGE_JSON || change.after === undefined) return [head]
|
|
132
|
+
|
|
133
|
+
return [head, ...package_json_lines(change).map((line) => ` ${line}`)]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function format_report(
|
|
137
|
+
title: string,
|
|
138
|
+
changes: ReadonlyArray<FileChange>,
|
|
139
|
+
dependency_lines: ReadonlyArray<string>,
|
|
140
|
+
): string {
|
|
141
|
+
const files =
|
|
142
|
+
changes.length === 0 ? [NO_CHANGES] : changes.flatMap((change) => change_lines(change))
|
|
143
|
+
const dependencies = dependency_lines.map((line) => ` ${line}`)
|
|
144
|
+
|
|
145
|
+
return [title, '', 'Files:', ...files, '', 'Dependencies:', ...dependencies].join('\n')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const init_dry_run_report = { changed_files, change_kind, package_json_lines, format_report }
|
|
149
|
+
export { init_dry_run_report }
|
|
150
|
+
export type { ChangeKind, FileChange }
|