@joshuafolkken/kit 1.1010.0 → 1.1035.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.
Files changed (272) hide show
  1. package/.claude/settings.json +1 -5
  2. package/.claude/skills/dependency-update/SKILL.md +6 -3
  3. package/.claude/skills/epic-commands/SKILL.md +46 -17
  4. package/.claude/skills/workflow-commands/SKILL.md +8 -23
  5. package/.claude/skills/workflow-commands/backlogrun-child.md +4 -2
  6. package/.claude/skills/workflow-commands/backlogrun.md +3 -12
  7. package/.claude/skills/workflow-commands/fullrun-steps.md +4 -3
  8. package/.claude/skills/workflow-commands/fullrun.md +1 -1
  9. package/.claude/skills/workflow-commands/halfrun.md +2 -1
  10. package/.claude/skills/workflow-commands/into-target.md +1 -1
  11. package/.claude/skills/workflow-commands/observation-filing.md +42 -43
  12. package/.claude/skills/workflow-commands/prerequisite.md +1 -1
  13. package/.codex/hooks.json +1 -5
  14. package/.github/workflows/sonar-qube.yml +1 -1
  15. package/AGENTS.md +4 -25
  16. package/CLAUDE.md +24 -38
  17. package/GEMINI.md +4 -24
  18. package/README.md +22 -56
  19. package/dist/CLAUDE.md +24 -38
  20. package/dist/hooks/batch-guard.js +4 -4
  21. package/dist/hooks/{chunk-WMIR5SNX.js → chunk-D67KVGGY.js} +3 -3
  22. package/dist/hooks/{chunk-EZLHXKJ6.js → chunk-EIFCAJRB.js} +70 -27
  23. package/dist/hooks/{chunk-KPZN42RT.js → chunk-GJT4BNVE.js} +3 -3
  24. package/dist/hooks/{chunk-PAXMLGSS.js → chunk-GLBDBJOK.js} +44 -24
  25. package/dist/hooks/{chunk-GBJUERYK.js → chunk-HOYJU5RM.js} +1 -1
  26. package/dist/hooks/{chunk-6PSTVOLH.js → chunk-HU66PWWL.js} +33 -57
  27. package/dist/hooks/{chunk-A2H5I2VD.js → chunk-KG6PZD72.js} +2 -2
  28. package/dist/hooks/{chunk-AWTFKKFW.js → chunk-XPLP27WA.js} +3 -8
  29. package/dist/hooks/{chunk-4GDR2YTV.js → chunk-Y27M6A7X.js} +4 -4
  30. package/dist/hooks/{chunk-56LGMJJV.js → chunk-ZXNE6NY3.js} +2 -2
  31. package/dist/hooks/codex-hook-adapter.js +10 -10
  32. package/dist/hooks/duplicate-read-guard.js +5 -5
  33. package/dist/hooks/format-edited.js +2 -2
  34. package/dist/hooks/investigation-guard.js +5 -5
  35. package/dist/hooks/pretool-guard.js +9 -9
  36. package/dist/hooks/stop-guard.js +11 -15
  37. package/dist/josh.js +38 -19
  38. package/dist/version/index.js +93 -41
  39. package/eslint/base.js +2 -11
  40. package/package.json +31 -5
  41. package/prompts/coding-standards.md +15 -290
  42. package/prompts/collaboration-workflow/cross-repo-epic.md +2 -51
  43. package/prompts/collaboration-workflow/epic-audit.md +2 -53
  44. package/prompts/collaboration-workflow/epic-bundle.md +2 -106
  45. package/prompts/collaboration-workflow/issue-template.md +4 -4
  46. package/prompts/collaboration-workflow/operating-rules.md +18 -75
  47. package/prompts/collaboration-workflow/principles.md +74 -0
  48. package/prompts/collaboration-workflow/residency.md +54 -14
  49. package/prompts/collaboration-workflow/rule-delivery.md +1 -11
  50. package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
  51. package/prompts/collaboration-workflow.md +22 -28
  52. package/scripts/adopt/adopt-logic.ts +2 -2
  53. package/scripts/adopt/adopt-toolkits.ts +1 -1
  54. package/scripts/adopt/adopt.ts +2 -2
  55. package/scripts/agent/agent-event.ts +2 -9
  56. package/scripts/agent/agent-exit-record.ts +2 -9
  57. package/scripts/backlog/backlog-budget-cli.ts +2 -6
  58. package/scripts/backlog/backlog-drive-cli.ts +20 -10
  59. package/scripts/backlog/backlog-drive.ts +50 -16
  60. package/scripts/backlog/backlog-offer-cli.ts +6 -5
  61. package/scripts/backlog/backlog-plan.ts +1 -1
  62. package/scripts/build/build-bin.ts +2 -1
  63. package/scripts/build/build-config-merge.ts +2 -1
  64. package/scripts/build/build-hooks.ts +24 -4
  65. package/scripts/build/build-managed-marker.ts +2 -1
  66. package/scripts/build/build-self-sync-guard.ts +2 -1
  67. package/scripts/build/build-version.ts +2 -1
  68. package/scripts/cases/cases-logic.ts +2 -6
  69. package/scripts/ci/pr-classification.ts +13 -11
  70. package/scripts/cost-runtime/cost-transcript.ts +4 -11
  71. package/scripts/doctor/doctor-consumer.ts +5 -12
  72. package/scripts/doctor/doctor-ports.ts +2 -2
  73. package/scripts/document/document-byte-budget.ts +8 -16
  74. package/scripts/document/document-section.ts +2 -6
  75. package/scripts/document/entry-read-budget.ts +1 -1
  76. package/scripts/{git/git-epic-add-body.ts → epic/epic-add-body.ts} +23 -23
  77. package/scripts/{git/git-epic-add-plan.ts → epic/epic-add-plan.ts} +23 -26
  78. package/scripts/{git/git-epic-add.ts → epic/epic-add.ts} +17 -17
  79. package/scripts/epic/epic-audit-cli.ts +4 -4
  80. package/scripts/epic/epic-audit-orphans.ts +2 -2
  81. package/scripts/epic/epic-audit-rationale.ts +1 -1
  82. package/scripts/epic/epic-audit.ts +1 -1
  83. package/scripts/{git/git-epic-body.ts → epic/epic-body.ts} +6 -6
  84. package/scripts/epic/epic-bundle-cli.ts +1 -1
  85. package/scripts/epic/epic-bundle.ts +2 -2
  86. package/scripts/epic/epic-candidate-confirm.ts +2 -1
  87. package/scripts/{git/git-epic-chains.ts → epic/epic-chains.ts} +4 -4
  88. package/{scripts-ai → scripts/epic}/epic-check.ts +5 -4
  89. package/{scripts-ai → scripts/epic}/epic-cli.ts +4 -4
  90. package/scripts/{git/git-epic-close-comment.ts → epic/epic-close-comment.ts} +9 -9
  91. package/scripts/{git/git-epic-close.ts → epic/epic-close.ts} +23 -19
  92. package/scripts/epic/epic-cross-repo.ts +21 -38
  93. package/scripts/{git/git-epic-decision.ts → epic/epic-decision.ts} +12 -14
  94. package/scripts/epic/epic-fetch.ts +3 -3
  95. package/scripts/epic/epic-graph.ts +3 -3
  96. package/scripts/epic/epic-index.ts +2 -2
  97. package/scripts/epic/epic-issue.ts +1 -1
  98. package/scripts/epic/epic-next.ts +3 -3
  99. package/scripts/{git/git-epic-parse.ts → epic/epic-parse.ts} +3 -3
  100. package/scripts/{git/git-epic-promote.ts → epic/epic-promote.ts} +10 -10
  101. package/scripts/{git/git-epic-read.ts → epic/epic-read.ts} +9 -9
  102. package/scripts/{git/git-epic-reconcile.ts → epic/epic-reconcile.ts} +18 -20
  103. package/scripts/{git/git-epic-reference.ts → epic/epic-reference.ts} +4 -23
  104. package/scripts/epic/epic-relation-recheck.ts +4 -3
  105. package/scripts/{git/git-epic-relations.ts → epic/epic-relations.ts} +5 -5
  106. package/scripts/{git/git-epic-remove-plan.ts → epic/epic-remove-plan.ts} +17 -19
  107. package/scripts/{git/git-epic-remove.ts → epic/epic-remove.ts} +13 -13
  108. package/scripts/{git/git-epic-run.ts → epic/epic-run.ts} +27 -27
  109. package/scripts/{git/git-epic-sections.ts → epic/epic-sections.ts} +6 -6
  110. package/scripts/{git/git-epic-shape.ts → epic/epic-shape.ts} +7 -7
  111. package/scripts/{git/git-epic-validate.ts → epic/epic-validate.ts} +13 -13
  112. package/{scripts-ai → scripts/epic}/epic.ts +17 -16
  113. package/scripts/gh/fix-gh-packages.ts +2 -1
  114. package/scripts/gh/gh-spawn.ts +12 -10
  115. package/scripts/git/constants.ts +1 -1
  116. package/scripts/git/git-branch.ts +10 -1
  117. package/scripts/git/git-command.ts +1 -1
  118. package/scripts/git/git-error.ts +2 -5
  119. package/{scripts-ai → scripts/git}/git-followup-finish.ts +7 -7
  120. package/scripts/git/git-followup-flush.ts +74 -48
  121. package/scripts/git/git-followup-issue-close.ts +2 -2
  122. package/scripts/git/git-followup-label.ts +1 -1
  123. package/scripts/git/git-followup-pending.ts +1 -1
  124. package/{scripts-ai → scripts/git}/git-followup-workflow.ts +23 -11
  125. package/scripts/git/git-gh-exec.ts +85 -26
  126. package/scripts/git/git-gh-issue-read.ts +2 -2
  127. package/scripts/git/git-gh-issue-rest.ts +2 -2
  128. package/scripts/git/git-gh-issue-write.ts +6 -6
  129. package/scripts/git/git-gh-pr-read.ts +0 -21
  130. package/scripts/git/git-gh-pr.ts +2 -23
  131. package/scripts/git/git-pr-followup-wrapup.ts +2 -6
  132. package/scripts/git/git-preflight.ts +3 -6
  133. package/scripts/git/git-staging.ts +21 -9
  134. package/{scripts-ai → scripts/git}/git-workflow.ts +11 -11
  135. package/scripts/git/github-issue-url.ts +1 -1
  136. package/scripts/git/issue-labels.ts +1 -1
  137. package/scripts/git/issue-reference.ts +19 -0
  138. package/scripts/git/main-merge.ts +2 -1
  139. package/scripts/git/main-sync.ts +3 -2
  140. package/scripts/git/schemas.ts +1 -1
  141. package/scripts/git/stash-pop-args.ts +7 -13
  142. package/scripts/git/stash-pop-cli.ts +2 -1
  143. package/{scripts-ai → scripts/git}/telegram-test-logic.ts +2 -2
  144. package/{scripts-ai → scripts/git}/telegram-test.ts +7 -7
  145. package/scripts/hooks/format-rewrite-notice.ts +2 -8
  146. package/scripts/init/init-ai-copy.ts +2 -5
  147. package/scripts/init/init.ts +8 -4
  148. package/scripts/init/start-steps.ts +2 -1
  149. package/scripts/init/start.ts +2 -1
  150. package/scripts/issue/issue-comment-cli.ts +2 -1
  151. package/scripts/issue/issue-file-cli.ts +2 -1
  152. package/scripts/issue/issue-fold-cli.ts +9 -16
  153. package/scripts/issue/issue-scout-cli.ts +8 -13
  154. package/scripts/issue/measure-rerun-cli.ts +7 -6
  155. package/scripts/josh/josh-commands-ai.ts +2 -2
  156. package/scripts/josh/josh-commands-backlog.ts +1 -1
  157. package/scripts/josh/josh-commands-development.ts +1 -1
  158. package/scripts/josh/josh-commands-workflow.ts +4 -4
  159. package/scripts/lane/lane-cli.ts +2 -1
  160. package/scripts/lane/lane-open.ts +3 -2
  161. package/scripts/lane/lane-registry.ts +3 -6
  162. package/scripts/lib/cli-flags.ts +20 -2
  163. package/scripts/lib/error-message.ts +9 -0
  164. package/scripts/lib/read-file.ts +26 -2
  165. package/scripts/observations/observation-ledger-home.ts +98 -26
  166. package/scripts/observations/observation-ledger-migrate.ts +26 -21
  167. package/scripts/observations/observation-ledger-prepare.ts +10 -20
  168. package/scripts/observations/observation-ledger.ts +58 -25
  169. package/scripts/observations/observations-flush-cli.ts +6 -23
  170. package/scripts/observations/observations-flush-landing.ts +2 -1
  171. package/scripts/observations/observations-flush.ts +29 -20
  172. package/scripts/package/package-scout-cli.ts +8 -16
  173. package/scripts/ports/port-command.ts +2 -1
  174. package/scripts/propagate/propagate-publish.ts +6 -7
  175. package/scripts/propagate/propagate-steps.ts +2 -1
  176. package/scripts/propagate/propagate.ts +3 -2
  177. package/scripts/refactor/import-graph.ts +2 -6
  178. package/scripts/registry-migration/migrate.ts +3 -2
  179. package/scripts/registry-migration/user-npmrc.ts +2 -2
  180. package/scripts/release/release-cli.ts +2 -1
  181. package/scripts/repo/repo-setting.ts +10 -8
  182. package/scripts/review/review-finding-ledger.ts +3 -3
  183. package/scripts/review/review-findings-cli.ts +4 -11
  184. package/scripts/review/review-record-cli.ts +14 -11
  185. package/scripts/review/review-record.ts +6 -16
  186. package/scripts/rules/decision-oracle.ts +2 -2
  187. package/scripts/rules/rule-body-guard.ts +2 -2
  188. package/scripts/rules/rule-value-cache.ts +99 -0
  189. package/scripts/rules/rule-value-cli.ts +10 -9
  190. package/scripts/rules/rule-value.ts +1 -1
  191. package/scripts/run/run-carry-args.ts +2 -6
  192. package/scripts/run/run-carry-cli.ts +5 -7
  193. package/scripts/run/run-cut-args.ts +7 -13
  194. package/scripts/run/run-ending-cli.ts +4 -7
  195. package/scripts/run/run-event-stream.ts +2 -5
  196. package/scripts/run/run-liveness-cli.ts +4 -7
  197. package/scripts/run/run-progress-args.ts +2 -6
  198. package/scripts/run/run-progress-cli.ts +2 -1
  199. package/scripts/run/run-tail-cli.ts +3 -3
  200. package/scripts/run/run-tidy-stashes.ts +7 -14
  201. package/scripts/run/run-wake-cli.ts +2 -6
  202. package/scripts/run/run-wake-driver.ts +15 -2
  203. package/scripts/run/run-wake-session.ts +14 -4
  204. package/scripts/safe-chain/project-config.ts +3 -6
  205. package/scripts/security/security-audit-provision.ts +6 -7
  206. package/scripts/sonar/sonar-hotspots-cli.ts +2 -1
  207. package/scripts/sync/directory-copy-guard.ts +2 -1
  208. package/scripts/sync/sync-ai-files.ts +3 -3
  209. package/scripts/sync/template-source-logic.ts +3 -4
  210. package/scripts/test/e2e-retry-check.ts +3 -6
  211. package/scripts/ui/ui-routes-cli.ts +3 -6
  212. package/scripts/version/bump-version.ts +2 -1
  213. package/scripts/version/version-remote.ts +18 -27
  214. package/scripts/version/version-targets.ts +3 -3
  215. package/.claude/skills/workflow-commands/rule-residency.md +0 -267
  216. package/evals/scenarios/consult-not-execute.json +0 -20
  217. package/evals/scenarios/no-implicit-workflow.json +0 -26
  218. package/evals/scenarios/overrides-both-locations.json +0 -29
  219. package/evals/scenarios/upstream-interrupt.json +0 -23
  220. package/evals/scenarios/workflow-skill-loaded.json +0 -16
  221. package/prompts/agent-lint-protocol.json +0 -79
  222. package/prompts/agent-rules.md +0 -63
  223. package/prompts/collaboration-workflow/consultation-vs-execution.md +0 -8
  224. package/prompts/collaboration-workflow/distributed-docs.md +0 -7
  225. package/prompts/collaboration-workflow/durable-rules.md +0 -24
  226. package/prompts/collaboration-workflow/latest-first.md +0 -14
  227. package/prompts/collaboration-workflow/no-clones.md +0 -11
  228. package/prompts/collaboration-workflow/simplicity-first.md +0 -9
  229. package/prompts/collaboration-workflow/single-source-rules.md +0 -13
  230. package/scripts/build/shim-shell.ts +0 -25
  231. package/scripts/claude/skill-meta.ts +0 -37
  232. package/scripts/document/document-scan.ts +0 -108
  233. package/scripts/document/generate-catalog.ts +0 -69
  234. package/scripts/document/retired-phrases.ts +0 -143
  235. package/scripts/document/review-agent-type.ts +0 -46
  236. package/scripts/document/single-source.ts +0 -73
  237. package/scripts/git/git-fixture-workspace.ts +0 -88
  238. package/scripts/hooks/format-edited-file-fixtures.ts +0 -109
  239. package/scripts/hooks/prompt-hooks.ts +0 -38
  240. package/scripts/init/hook-command-bootstrap.ts +0 -83
  241. package/scripts/issue/issue-logic.ts +0 -50
  242. package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +0 -1227
  243. package/scripts/josh/josh-script-reader.ts +0 -11
  244. package/scripts/package/production-dependency-graph.ts +0 -68
  245. package/scripts/retrospective/retrospective-cli.ts +0 -97
  246. package/scripts/retrospective/retrospective.ts +0 -127
  247. package/scripts/rules/delivered-rules-harness.ts +0 -72
  248. package/scripts/rules/top-level-scripts-guard.ts +0 -47
  249. package/scripts/run/run-carry-flush.ts +0 -58
  250. package/scripts/run/run-transcript.golden.txt +0 -57
  251. package/scripts/run/run-wake-work.ts +0 -108
  252. package/scripts/sync/dependabot-pin-logic.ts +0 -135
  253. package/scripts/test/classify-isolation.ts +0 -149
  254. package/scripts/test/http-fault-injection.ts +0 -79
  255. package/scripts/test/josh-harness-environment.ts +0 -260
  256. package/scripts/test/josh-harness.ts +0 -96
  257. package/scripts/test/pilot-files.ts +0 -452
  258. package/scripts/test/pilot-measure.ts +0 -78
  259. package/scripts/test/test-network-guard.ts +0 -453
  260. package/scripts/test/test-repository-guard.ts +0 -317
  261. package/scripts/test/test-state-guard.ts +0 -85
  262. package/scripts/test/test-stdout-guard.ts +0 -54
  263. package/scripts/test/test-telegram-guard.ts +0 -90
  264. package/scripts/test/unit-guard-environment.ts +0 -12
  265. package/scripts/test/unit-projects.ts +0 -139
  266. package/scripts/test/vitest-include-globs.ts +0 -12
  267. package/scripts/time-runtime/time-batch-guard-fixtures.ts +0 -66
  268. package/scripts-ai/completion-gate.ts +0 -13
  269. package/scripts-ai/environment-loader.ts +0 -9
  270. /package/{scripts-ai → scripts/epic}/epic-cli-argv.ts +0 -0
  271. /package/{scripts-ai → scripts/epic}/epic-cli-remove.ts +0 -0
  272. /package/{scripts-ai → scripts/git}/followup-issue-number.ts +0 -0
@@ -1,135 +0,0 @@
1
- import type { PinDrift } from './workflow-pin-logic'
2
-
3
- // Orchestration for `josh sync-dependabot-pins`: propagate a Dependabot action
4
- // bump from .github/workflows into templates/workflows for one or more PRs.
5
- // All git/gh side effects are injected via DependabotPinOps so the control flow
6
- // (checkout order, drift-skip, dry-run no-op) is unit-testable without spawning.
7
- const NO_PR_MESSAGE = 'No PR numbers supplied (e.g. `josh sdp 578 641`)'
8
- const INVALID_PR_PREFIX = 'Invalid PR number: '
9
- const DEPENDABOT_BRANCH_PREFIX = 'dependabot/'
10
- const PR_NUMBER_PATTERN = /^[1-9]\d*$/u
11
-
12
- interface DependabotPinOps {
13
- get_current_branch: () => Promise<string>
14
- get_pr_branch: (pr: number) => Promise<string>
15
- checkout_pr: (pr: number) => Promise<void>
16
- checkout_branch: (branch: string) => Promise<void>
17
- sync_pins: () => Array<PinDrift>
18
- stage_templates: () => Promise<void>
19
- commit: (message: string) => Promise<void>
20
- push: () => Promise<void>
21
- log: (message: string) => void
22
- }
23
-
24
- interface SyncOptions {
25
- is_dry_run: boolean
26
- }
27
-
28
- interface PrResult {
29
- pr: number
30
- synced_count: number
31
- is_committed: boolean
32
- }
33
-
34
- function build_commit_message(pr: number): string {
35
- return `Sync template workflow pins (Dependabot #${String(pr)})`
36
- }
37
-
38
- function parse_pr_number(token: string): number {
39
- if (!PR_NUMBER_PATTERN.test(token)) throw new Error(`${INVALID_PR_PREFIX}${token}`)
40
-
41
- const pr = Number(token)
42
- if (!Number.isSafeInteger(pr)) throw new Error(`${INVALID_PR_PREFIX}${token}`)
43
-
44
- return pr
45
- }
46
-
47
- function parse_pr_numbers(tokens: ReadonlyArray<string>): Array<number> {
48
- if (tokens.length === 0) throw new Error(NO_PR_MESSAGE)
49
-
50
- return tokens.map((token) => parse_pr_number(token))
51
- }
52
-
53
- async function commit_synced_pins(pr: number, ops: DependabotPinOps): Promise<void> {
54
- await ops.stage_templates()
55
- await ops.commit(build_commit_message(pr))
56
- await ops.push()
57
- }
58
-
59
- function skipped_result(pr: number): PrResult {
60
- return { pr, synced_count: 0, is_committed: false }
61
- }
62
-
63
- async function checkout_dependabot_pr(pr: number, ops: DependabotPinOps): Promise<boolean> {
64
- await ops.checkout_pr(pr)
65
- const branch = await ops.get_current_branch()
66
-
67
- if (branch.startsWith(DEPENDABOT_BRANCH_PREFIX)) return true
68
-
69
- ops.log(`#${String(pr)}: ${branch} is not a Dependabot branch — skipped`)
70
-
71
- return false
72
- }
73
-
74
- async function apply_pr(pr: number, ops: DependabotPinOps): Promise<PrResult> {
75
- if (!(await checkout_dependabot_pr(pr, ops))) return skipped_result(pr)
76
-
77
- const drifts = ops.sync_pins()
78
-
79
- if (drifts.length === 0) {
80
- ops.log(`#${String(pr)}: template pins already in sync — skipped`)
81
-
82
- return skipped_result(pr)
83
- }
84
-
85
- await commit_synced_pins(pr, ops)
86
- ops.log(`#${String(pr)}: synced ${String(drifts.length)} pin(s) and pushed`)
87
-
88
- return { pr, synced_count: drifts.length, is_committed: true }
89
- }
90
-
91
- async function plan_pr(pr: number, ops: DependabotPinOps): Promise<PrResult> {
92
- const branch = await ops.get_pr_branch(pr)
93
-
94
- ops.log(`#${String(pr)} (${branch}): would sync template pins and push if drifted [dry-run]`)
95
-
96
- return skipped_result(pr)
97
- }
98
-
99
- async function process_pr(
100
- pr: number,
101
- options: SyncOptions,
102
- ops: DependabotPinOps,
103
- ): Promise<PrResult> {
104
- if (options.is_dry_run) return await plan_pr(pr, ops)
105
-
106
- return await apply_pr(pr, ops)
107
- }
108
-
109
- async function run_sync(
110
- prs: ReadonlyArray<number>,
111
- options: SyncOptions,
112
- ops: DependabotPinOps,
113
- ): Promise<Array<PrResult>> {
114
- const start_branch = await ops.get_current_branch()
115
- const results: Array<PrResult> = []
116
-
117
- try {
118
- for (const pr of prs) {
119
- results.push(await process_pr(pr, options, ops))
120
- }
121
- } finally {
122
- if (!options.is_dry_run) await ops.checkout_branch(start_branch)
123
- }
124
-
125
- return results
126
- }
127
-
128
- const dependabot_pin_logic = {
129
- build_commit_message,
130
- parse_pr_numbers,
131
- run_sync,
132
- }
133
-
134
- export { dependabot_pin_logic }
135
- export type { DependabotPinOps }
@@ -1,149 +0,0 @@
1
- #!/usr/bin/env tsx
2
- import { globSync, readFileSync, writeFileSync } from 'node:fs'
3
- import path from 'node:path'
4
- import { fileURLToPath } from 'node:url'
5
- import { VITEST_INCLUDE_GLOBS } from './vitest-include-globs'
6
-
7
- // Isolation requirements — a test matching ANY condition below mutates state a sibling test running
8
- // in the same worker could observe, so it must run with the default isolate:true and must NOT appear
9
- // in the generated pilot list. The classifier is deliberately conservative: a file it cannot decide
10
- // is left isolated, because a false "pure" costs a flaky pilot while a false "isolate" costs only a
11
- // little speed.
12
- // 1. Module mocks: vi.mock / vi.spyOn / vi.stubEnv / vi.stubGlobal / vi.resetModules /
13
- // vi.unstubAllEnvs / vi.unstubAllGlobals
14
- // 2. Environment variable writes: process.env.X = ... or delete process.env.X
15
- // 3. Subprocess calls: execSync / spawnSync / spawn / exec / execa / child_process.*
16
- // 4. Filesystem writes: writeFileSync / mkdirSync / mkdtemp / rmSync / unlinkSync / rimraf
17
- // 5. Git operations: enforced by condition 3 — every git operation spawns a process, so a file
18
- // that runs git already matches the subprocess pattern.
19
-
20
- const MOCK_TOKENS: ReadonlyArray<string> = [
21
- 'mock',
22
- 'spyOn',
23
- 'stubEnv',
24
- 'stubGlobal',
25
- 'resetModules',
26
- 'unstubAllEnvs',
27
- 'unstubAllGlobals',
28
- ]
29
- // Bare `exec` is deliberately absent: it matches the pure word (`pnpm exec …`, `regex.exec(…)`) more
30
- // often than the child_process call, which an `execSync` / `child_process` token catches anyway.
31
- const SUBPROCESS_TOKENS: ReadonlyArray<string> = [
32
- 'execSync',
33
- 'execFileSync',
34
- 'spawnSync',
35
- 'execaSync',
36
- 'execa',
37
- 'spawn',
38
- 'child_process',
39
- ]
40
- const FS_WRITE_TOKENS: ReadonlyArray<string> = [
41
- 'writeFileSync',
42
- 'writeFile',
43
- 'appendFileSync',
44
- 'mkdirSync',
45
- 'mkdtempSync',
46
- 'mkdtemp',
47
- 'rmSync',
48
- 'rmdirSync',
49
- 'unlinkSync',
50
- 'cpSync',
51
- 'copyFileSync',
52
- 'renameSync',
53
- 'rimraf',
54
- ]
55
-
56
- function word_pattern(tokens: ReadonlyArray<string>): RegExp {
57
- return new RegExp(String.raw`\b(?:${tokens.join('|')})\b`, 'u')
58
- }
59
-
60
- const MODULE_MOCKS = new RegExp(String.raw`\bvi\.(?:${MOCK_TOKENS.join('|')})\b`, 'u')
61
- const ENV_WRITES =
62
- /(?:process\.env\.\w+\s*=(?!=)|process\.env\[[^\]]+\]\s*=(?!=)|delete\s+process\.env)/u
63
- const SUBPROCESS = word_pattern(SUBPROCESS_TOKENS)
64
- const FS_WRITES = word_pattern(FS_WRITE_TOKENS)
65
-
66
- const ISOLATION_PATTERNS: ReadonlyArray<RegExp> = [MODULE_MOCKS, ENV_WRITES, SUBPROCESS, FS_WRITES]
67
-
68
- // Excluded from the main suite in vitest.config.ts (packs and installs the real tarball), so it must
69
- // never reach the pilot either.
70
- const MAIN_EXCLUDE = new Set(['scripts/build/packed-consumer.test.ts'])
71
- const NODE_MODULES = 'node_modules'
72
- const PILOT_FILES_PATH = 'scripts/test/pilot-files.ts'
73
- const WRITE_FLAG = '--write'
74
- const INDENT = '\t'
75
- const UTF8 = 'utf8'
76
-
77
- const GENERATED_HEADER = `/* eslint-disable max-lines */
78
- // GENERATED FILE — do not edit by hand.
79
- // Regenerate with: pnpm exec tsx scripts/test/classify-isolation.ts --write
80
- // Every entry is a test file the classifier judges free of the isolation requirements documented in
81
- // classify-isolation.ts; these files run with isolate:false in vitest.pilot.config.ts.`
82
-
83
- function normalize_path(file: string): string {
84
- return file.split(path.sep).join('/')
85
- }
86
-
87
- function is_in_node_modules(entry: string): boolean {
88
- return entry.split(/[/\\]/u).includes(NODE_MODULES)
89
- }
90
-
91
- function collect_glob(glob: string): Array<string> {
92
- return globSync(glob, { exclude: is_in_node_modules }).map((file) => normalize_path(file))
93
- }
94
-
95
- // Code-point order, never localeCompare: the list is generated on one machine and re-derived in CI,
96
- // and ICU collation differs across platforms — so localeCompare reorders the list under CI's Linux
97
- // ICU and fails the drift test on order alone (joshuafolkken/kit#2170).
98
- function by_code_point(left: string, right: string): number {
99
- if (left < right) return -1
100
-
101
- return left > right ? 1 : 0
102
- }
103
-
104
- function all_test_files(): Array<string> {
105
- const files = VITEST_INCLUDE_GLOBS.flatMap((glob) => collect_glob(glob))
106
-
107
- return [...new Set(files)].filter((file) => !MAIN_EXCLUDE.has(file)).toSorted(by_code_point)
108
- }
109
-
110
- function requires_isolation(source: string): boolean {
111
- return ISOLATION_PATTERNS.some((pattern) => pattern.test(source))
112
- }
113
-
114
- function is_pilot_candidate(file: string): boolean {
115
- return !requires_isolation(readFileSync(file, UTF8))
116
- }
117
-
118
- function classify_pilot_files(): Array<string> {
119
- return all_test_files().filter((file) => is_pilot_candidate(file))
120
- }
121
-
122
- function render_pilot_files(files: ReadonlyArray<string>): string {
123
- const entries = files.map((file) => `${INDENT}'${file}',`).join('\n')
124
- const body = `const PILOT_FILES: ReadonlyArray<string> = [\n${entries}\n]`
125
-
126
- return `${GENERATED_HEADER}\n\n${body}\n\nexport { PILOT_FILES }\n`
127
- }
128
-
129
- function write_pilot_files(): void {
130
- writeFileSync(PILOT_FILES_PATH, render_pilot_files(classify_pilot_files()))
131
- }
132
-
133
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
134
- if (process.argv.includes(WRITE_FLAG)) write_pilot_files()
135
- else process.stdout.write(`${classify_pilot_files().join('\n')}\n`)
136
- }
137
-
138
- const classify_isolation = {
139
- ISOLATION_PATTERNS,
140
- all_test_files,
141
- by_code_point,
142
- classify_pilot_files,
143
- is_pilot_candidate,
144
- render_pilot_files,
145
- requires_isolation,
146
- write_pilot_files,
147
- }
148
-
149
- export { classify_isolation }
@@ -1,79 +0,0 @@
1
- import { cases } from '#scripts/cases/cases-logic'
2
-
3
- // joshuafolkken/kit#2355: the network boundary's abnormal cases were declared and never run. The unit
4
- // suite blocks the network by construction (`test-network-guard.ts` shims the `gh` and `git`
5
- // binaries), so `josh cases`' network vocabulary had no way to be *executed* — only named in a test
6
- // declaration, where the failure mode nobody anticipated is exactly the one that never gets declared
7
- // (the transport failure of joshuafolkken/kit#2317 was hit before it was foreseen).
8
- //
9
- // This makes each of those failure modes injectable as a `fetch` stand-in that produces the failure
10
- // without opening a connection. It does not weaken the guard: the shims are untouched, no real request
11
- // leaves the machine, and the stand-in only shapes the `Response` (or rejection) the caller's error
12
- // path already has to survive. The mode set is single-sourced from `cases`, so the vocabulary the
13
- // command prints and the failures that can be injected cannot drift apart — `INJECTORS` is asserted
14
- // exhaustive against it in the colocated test.
15
-
16
- // A `fetch` stand-in: the real signature takes an input and an optional init, both ignored here since
17
- // the failure is fixed by the mode rather than the request.
18
- type FetchStub = (input?: unknown, init?: unknown) => Promise<Response>
19
-
20
- const NON_200_STATUS = 500
21
- const RATE_LIMIT_STATUS = 429
22
- const OK_STATUS = 200
23
-
24
- // The two rejection failures wear the shapes the platform itself throws, so a caller's `catch` sees
25
- // what a real timeout or dropped connection would produce: `AbortSignal.timeout` rejects with a
26
- // `TimeoutError` DOMException, and a transport failure rejects with a `TypeError`.
27
- const TIMEOUT_MESSAGE = 'The operation timed out.'
28
- const TIMEOUT_NAME = 'TimeoutError'
29
- const CONNECTION_DROP_MESSAGE = 'fetch failed'
30
- // A body that is present but not JSON, told apart from the empty body by having content at all.
31
- const INVALID_JSON_BODY = '{'
32
- const EMPTY_BODY = ''
33
-
34
- function ok_body(body: string): FetchStub {
35
- return async () => new Response(body, { status: OK_STATUS })
36
- }
37
-
38
- function non_ok(code: number): FetchStub {
39
- return async () => new Response(EMPTY_BODY, { status: code })
40
- }
41
-
42
- function rejecting(error: Error): FetchStub {
43
- return async () => {
44
- throw error
45
- }
46
- }
47
-
48
- // Keyed by the exact vocabulary term `josh cases` prints for the network boundary. The colocated test
49
- // proves this key set equals that vocabulary, so a term added there without an injector here fails.
50
- const INJECTORS: Readonly<Record<string, FetchStub>> = {
51
- 非200: non_ok(NON_200_STATUS),
52
- タイムアウト: rejecting(new DOMException(TIMEOUT_MESSAGE, TIMEOUT_NAME)),
53
- 接続断: rejecting(new TypeError(CONNECTION_DROP_MESSAGE)),
54
- 空レスポンス: ok_body(EMPTY_BODY),
55
- 不正JSON: ok_body(INVALID_JSON_BODY),
56
- レート制限: non_ok(RATE_LIMIT_STATUS),
57
- }
58
-
59
- // The failure modes that can be injected — the injector key set, for the correspondence assertion.
60
- function supported_modes(): ReadonlyArray<string> {
61
- return Object.keys(INJECTORS)
62
- }
63
-
64
- // A `fetch` stand-in producing the named failure. An unknown mode throws rather than silently passing:
65
- // a typo in a test must not degrade into a request that quietly does nothing.
66
- function faulty_fetch(mode: string): FetchStub {
67
- const injector = INJECTORS[mode]
68
- if (injector === undefined) throw new Error(`unknown network failure mode: ${mode}`)
69
-
70
- return injector
71
- }
72
-
73
- // The network vocabulary this injector must cover, single-sourced from the command's own definition.
74
- const NETWORK_MODES: ReadonlyArray<string> = cases.cases_for(['network'])
75
-
76
- const http_fault_injection = { NETWORK_MODES, faulty_fetch, supported_modes }
77
-
78
- export type { FetchStub }
79
- export { http_fault_injection }
@@ -1,260 +0,0 @@
1
- import { execFileSync } from 'node:child_process'
2
- import {
3
- existsSync,
4
- mkdirSync,
5
- mkdtempSync,
6
- readFileSync,
7
- realpathSync,
8
- rmSync,
9
- symlinkSync,
10
- writeFileSync,
11
- } from 'node:fs'
12
- import { tmpdir } from 'node:os'
13
- import path from 'node:path'
14
- import { build_bin } from '#scripts/build/build-bin'
15
- import { resolve_local_bin } from '#scripts/build/local-bin'
16
- import { git_fixture_workspace } from '#scripts/git/git-fixture-workspace'
17
- import { josh_cli_fixture, type JoshLauncher } from '#scripts/josh/josh-cli-fixture'
18
-
19
- // Most defects reach a run at an environment boundary rather than inside a function
20
- // (joshuafolkken/kit#2447): a consumer without `docs/` (#2402), a lane worktree whose ledger lives in
21
- // the primary checkout (#2419), two processes racing on one marker (#2434). Unit tests call functions
22
- // inside this checkout, so none of those boundaries exists for them. Each environment here is a real
23
- // directory in the system temp dir, assembled the way a run meets it, and josh is spawned into it as a
24
- // subprocess — so a test asserts on the exit code, the output and the files the command left behind.
25
- //
26
- // **Every environment is gate-green on its own.** The toolchain files below are the least a project
27
- // needs for `josh gate` to pass in a few seconds, and `node_modules` is this checkout's, linked rather
28
- // than installed — so a scenario can run the real gate, not a stand-in for it.
29
-
30
- type EnvironmentKind = 'consumer' | 'kit' | 'lane' | 'packed'
31
-
32
- interface JoshEnvironment {
33
- kind: EnvironmentKind
34
- // Where the commands run — the lane checkout for a lane, the project root otherwise.
35
- root: string
36
- // The primary checkout: the same directory as `root` everywhere but a lane.
37
- primary: string
38
- launcher: JoshLauncher
39
- // The temp directory that holds everything above, removed whole on close.
40
- workspace: string
41
- }
42
-
43
- const PACKAGE_NAME = '@joshuafolkken/kit'
44
- const PRIMARY_DIRECTORY = 'primary'
45
- const LANE_DIRECTORY = 'lane'
46
- const LANE_BRANCH = 'lane'
47
- const NODE_MODULES = 'node_modules'
48
- const JOSH_BIN = 'josh'
49
- const PACKAGE_JSON = 'package.json'
50
- const WORKSPACE_PREFIX = 'josh-harness-'
51
- const JSON_INDENT = '\t'
52
- // pnpm 11 fails an install whose dependencies carry build scripts nobody approved; the throwaway
53
- // consumer approves none, and the smoke needs none of them run.
54
- const NON_STRICT_BUILDS = '--config.strict-dep-builds=false'
55
-
56
- function json_file(value: unknown): string {
57
- return `${JSON.stringify(value, undefined, JSON_INDENT)}\n`
58
- }
59
-
60
- // A command josh runs inside the environment — the gate's `pnpm josh lint` and the rest — must reach
61
- // this checkout's source, as the kit's own `josh` script and a consumer's installed bin do. Without the
62
- // script it fell through to whatever `josh` was on PATH: a global install locally, nothing at all in CI.
63
- // The kit's `packageManager` goes with it, so Corepack runs the pinned pnpm rather than fetching the latest.
64
- function launch_command(launcher: JoshLauncher): string {
65
- return [launcher.executable, ...launcher.leading_arguments]
66
- .map((part) => JSON.stringify(part))
67
- .join(' ')
68
- }
69
-
70
- function kit_package_manager(): string {
71
- const manifest = readFileSync(path.join(josh_cli_fixture.REPO_ROOT, PACKAGE_JSON), 'utf8')
72
-
73
- return (JSON.parse(manifest) as { packageManager: string }).packageManager
74
- }
75
-
76
- const PROJECT_FIELDS = {
77
- private: true,
78
- type: 'module',
79
- packageManager: kit_package_manager(),
80
- scripts: { josh: launch_command(josh_cli_fixture.SOURCE_JOSH) },
81
- }
82
-
83
- // A file the fixture writes: its path relative to the project root, and its content.
84
- type FixtureFile = readonly [string, string]
85
-
86
- const TOOLCHAIN_FILES: ReadonlyArray<FixtureFile> = [
87
- ['.gitignore', 'node_modules\n.*cache\n.tsbuildinfo\npnpm-lock.yaml\n'],
88
- ['.prettierignore', '*.json\npnpm-lock.yaml\n_tmp_*\n'],
89
- ['.prettierrc', json_file({ semi: false, singleQuote: true, useTabs: true })],
90
- [
91
- 'cspell.config.yaml',
92
- // `package.json` is skipped because its `josh` script carries this checkout's absolute paths.
93
- 'words:\n - joshuafolkken\nignorePaths:\n - .git\n - node_modules\n - .tsbuildinfo\n - .*cache\n - package.json\n - pnpm-lock.yaml\n',
94
- ],
95
- ['eslint.config.js', 'export default [{}]\n'],
96
- [
97
- 'sample.test.ts',
98
- "import { expect, it } from 'vitest'\n\nit('passes', () => {\n\texpect(1).toBe(1)\n})\n",
99
- ],
100
- [
101
- 'tsconfig.json',
102
- json_file({
103
- compilerOptions: { strict: true, noEmit: true, module: 'nodenext', types: [] },
104
- include: ['*.ts'],
105
- }),
106
- ],
107
- ]
108
-
109
- // The kit's own shape: its package name, so josh treats the directory as the kit rather than a
110
- // consumer, and the `docs/` and `prompts/` directories only the kit carries.
111
- const KIT_FILES: ReadonlyArray<FixtureFile> = [
112
- ...TOOLCHAIN_FILES,
113
- [PACKAGE_JSON, json_file({ name: PACKAGE_NAME, version: '0.0.0', ...PROJECT_FIELDS })],
114
- ['docs/README.md', '# Docs\n'],
115
- ['prompts/README.md', '# Prompts\n'],
116
- ]
117
-
118
- // The least a consumer is: a project that depends on the kit and carries neither `docs/` nor
119
- // `prompts/` — the shape #2402's ENOENT needed.
120
- const CONSUMER_FILES: ReadonlyArray<FixtureFile> = [
121
- ...TOOLCHAIN_FILES,
122
- [
123
- PACKAGE_JSON,
124
- json_file({
125
- name: 'kit-consumer',
126
- version: '0.0.0',
127
- ...PROJECT_FIELDS,
128
- devDependencies: { [PACKAGE_NAME]: '*' },
129
- }),
130
- ],
131
- ]
132
-
133
- // Resolved through `realpath` because a child's `process.cwd()` is: on macOS the temp dir is a
134
- // symlink, and a path the test built would not match the one the command printed or keyed a record on.
135
- function open_workspace(): string {
136
- const workspace = mkdtempSync(path.join(tmpdir(), WORKSPACE_PREFIX))
137
-
138
- return realpathSync(workspace)
139
- }
140
-
141
- function write_files(directory: string, files: ReadonlyArray<FixtureFile>): void {
142
- for (const [relative_path, content] of files) {
143
- const file = path.join(directory, relative_path)
144
-
145
- mkdirSync(path.dirname(file), { recursive: true })
146
- writeFileSync(file, content, 'utf8')
147
- }
148
- }
149
-
150
- function link_toolchain(directory: string): void {
151
- symlinkSync(
152
- path.join(josh_cli_fixture.REPO_ROOT, NODE_MODULES),
153
- path.join(directory, NODE_MODULES),
154
- 'dir',
155
- )
156
- }
157
-
158
- async function assemble(directory: string, files: ReadonlyArray<FixtureFile>): Promise<void> {
159
- const { git, MAIN_BRANCH } = git_fixture_workspace
160
-
161
- write_files(directory, files)
162
- link_toolchain(directory)
163
- await git(directory, ['init', MAIN_BRANCH])
164
- await git(directory, ['add', '-A'])
165
- await git(directory, ['commit', '-m', 'fixture'])
166
- }
167
-
168
- // A lane is a `git worktree` of the primary checkout, as `josh lane` makes one. `node_modules` is
169
- // ignored, so the worktree gets its own link rather than inheriting one.
170
- async function add_lane(primary: string, lane: string): Promise<string> {
171
- await git_fixture_workspace.git(primary, ['worktree', 'add', lane, '-b', LANE_BRANCH])
172
- link_toolchain(lane)
173
-
174
- return lane
175
- }
176
-
177
- // A setup that throws returns no environment, so nothing would ever close its workspace — it is
178
- // removed here before the error travels on.
179
- async function in_workspace<T>(build: (workspace: string) => T | Promise<T>): Promise<T> {
180
- const workspace = open_workspace()
181
-
182
- try {
183
- return await build(workspace)
184
- } catch (error) {
185
- rmSync(workspace, { recursive: true, force: true })
186
- throw error
187
- }
188
- }
189
-
190
- async function assemble_environment(
191
- kind: Exclude<EnvironmentKind, 'packed'>,
192
- workspace: string,
193
- ): Promise<JoshEnvironment> {
194
- const primary = path.join(workspace, PRIMARY_DIRECTORY)
195
-
196
- await assemble(primary, kind === 'consumer' ? CONSUMER_FILES : KIT_FILES)
197
- const root =
198
- kind === 'lane' ? await add_lane(primary, path.join(workspace, LANE_DIRECTORY)) : primary
199
-
200
- return { kind, root, primary, launcher: josh_cli_fixture.SOURCE_JOSH, workspace }
201
- }
202
-
203
- async function open_environment(
204
- kind: Exclude<EnvironmentKind, 'packed'>,
205
- ): Promise<JoshEnvironment> {
206
- return await in_workspace(async (workspace) => await assemble_environment(kind, workspace))
207
- }
208
-
209
- function pack_into(directory: string): string {
210
- const pack_output = execFileSync(
211
- 'pnpm',
212
- ['pack', '--json', '--pack-destination', directory, '--config.ignore-scripts=true'],
213
- { cwd: josh_cli_fixture.REPO_ROOT, encoding: 'utf8' },
214
- )
215
- const { filename } = JSON.parse(pack_output) as { filename: string }
216
-
217
- // pnpm 11 reports the tarball's absolute path, earlier versions its bare name; `resolve` takes both.
218
- return path.resolve(directory, filename)
219
- }
220
-
221
- // The published package rather than the source: the real tarball packed and installed into a bare
222
- // consumer, and josh run through the `node_modules/.bin` shim that install writes — the command a
223
- // consumer types. Not `resolve_package_bin`: the kit's `exports` map does not export its
224
- // `package.json`, so that route answers nothing for the kit itself. Slow (~60 s), so its suite stays
225
- // out of the unit gate.
226
- function install_packed(workspace: string): JoshEnvironment {
227
- writeFileSync(
228
- path.join(workspace, PACKAGE_JSON),
229
- json_file({ name: 'test-consumer', version: '1.0.0' }),
230
- )
231
- const tarball = pack_into(workspace)
232
-
233
- execFileSync('pnpm', ['add', NON_STRICT_BUILDS, tarball], { cwd: workspace, encoding: 'utf8' })
234
- const shim = resolve_local_bin(workspace, JOSH_BIN)
235
- if (!existsSync(shim)) throw new Error(`josh bin not found after installing ${tarball}`)
236
-
237
- const launcher: JoshLauncher = { executable: shim, leading_arguments: [] }
238
-
239
- return { kind: 'packed', root: workspace, primary: workspace, launcher, workspace }
240
- }
241
-
242
- async function open_packed_consumer(): Promise<JoshEnvironment> {
243
- await build_bin()
244
-
245
- return await in_workspace(install_packed)
246
- }
247
-
248
- function close_environment(environment: JoshEnvironment): void {
249
- rmSync(environment.workspace, { recursive: true, force: true })
250
- }
251
-
252
- const josh_harness_environment = {
253
- close_environment,
254
- in_workspace,
255
- open_environment,
256
- open_packed_consumer,
257
- }
258
-
259
- export type { EnvironmentKind, JoshEnvironment }
260
- export { josh_harness_environment }