@joshuafolkken/kit 1.883.0 → 1.921.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 (112) hide show
  1. package/.claude/skills/workflow-commands/fullrun-steps.md +4 -2
  2. package/.claude/skills/workflow-commands/halfrun.md +3 -1
  3. package/.claude/skills/workflow-commands/kickoff.md +5 -3
  4. package/.claude/skills/workflow-commands/observation-filing.md +3 -1
  5. package/.claude/skills/workflow-commands/prerequisite.md +3 -1
  6. package/.claude/skills/workflow-commands/target-repository.md +1 -1
  7. package/.github/pull_request_template.md +12 -0
  8. package/.github/release.yml +3 -0
  9. package/.github/workflows/ci.yml +8 -0
  10. package/.github/workflows/production.yml +10 -1
  11. package/.github/workflows/publish.yml +72 -2
  12. package/CLAUDE.md +2 -0
  13. package/dist/CLAUDE.md +2 -0
  14. package/dist/CLAUDE.static.md +8 -0
  15. package/dist/hooks/batch-guard.js +4 -4
  16. package/dist/hooks/{chunk-36UE53LS.js → chunk-3HZ5MJIW.js} +2 -2
  17. package/dist/hooks/chunk-3V37IID6.js +82 -0
  18. package/dist/hooks/{chunk-FRJKVSK3.js → chunk-77RLKZ5V.js} +2 -2
  19. package/dist/hooks/{chunk-VGYSA2NT.js → chunk-LC765SUA.js} +16 -2
  20. package/dist/hooks/{chunk-ZBK4XRAS.js → chunk-NWD47MW3.js} +3 -3
  21. package/dist/hooks/{chunk-4OXFU3MK.js → chunk-O2FBZPSW.js} +619 -156
  22. package/dist/hooks/{chunk-6MQCXEEA.js → chunk-OLTAGGHH.js} +25 -88
  23. package/dist/hooks/{chunk-FNFOLFFV.js → chunk-OYU6KPRA.js} +1 -1
  24. package/dist/hooks/{chunk-PRDCNGA6.js → chunk-UANRLZV7.js} +4 -4
  25. package/dist/hooks/{chunk-264QY6KS.js → chunk-UULARQ5Q.js} +23 -1
  26. package/dist/hooks/{chunk-Y5OYCU2F.js → chunk-XMR3BS35.js} +3 -3
  27. package/dist/hooks/codex-hook-adapter.js +11 -10
  28. package/dist/hooks/duplicate-read-guard.js +5 -5
  29. package/dist/hooks/format-edited.js +3 -2
  30. package/dist/hooks/investigation-guard.js +5 -5
  31. package/dist/hooks/pretool-guard.js +10 -9
  32. package/dist/hooks/stop-guard.js +3 -3
  33. package/dist/josh.js +155 -12
  34. package/package.json +70 -19
  35. package/prettier/index.js +3 -8
  36. package/prettier/static.d.ts +3 -0
  37. package/prettier/static.js +10 -0
  38. package/prompts/collaboration-workflow/issue-template.md +11 -4
  39. package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
  40. package/prompts/collaboration-workflow/wip-cap.md +2 -2
  41. package/prompts/review.md +2 -2
  42. package/prompts/testing-guide.md +1 -0
  43. package/scripts/build/build-claude-md.ts +6 -1
  44. package/scripts/ci/pr-classification.ts +114 -0
  45. package/scripts/document/document-byte-budget.ts +3 -2
  46. package/scripts/gate/gate-report.ts +2 -0
  47. package/scripts/gate/gate-steps.ts +50 -0
  48. package/scripts/gate/project-checks.ts +121 -0
  49. package/scripts/gate/verification-gate.ts +14 -43
  50. package/scripts/git/git-common-directory.ts +9 -1
  51. package/scripts/git/git-gh-pr-rollup.ts +29 -1
  52. package/scripts/git/git-gh-pr.ts +61 -1
  53. package/scripts/git/git-pr.ts +62 -13
  54. package/scripts/git/issue-labels.ts +9 -0
  55. package/scripts/init/distributed-paths.ts +9 -5
  56. package/scripts/init/hook-command-bootstrap.ts +40 -8
  57. package/scripts/init/hook-command-rewrite.ts +7 -3
  58. package/scripts/init/init-actions.ts +94 -7
  59. package/scripts/init/init-ai-copy.ts +104 -19
  60. package/scripts/init/init-static.ts +52 -0
  61. package/scripts/init/init.ts +97 -30
  62. package/scripts/init/project-profile-cli.ts +14 -0
  63. package/scripts/init/project-profile.ts +149 -0
  64. package/scripts/init/start.ts +30 -0
  65. package/scripts/issue/issue-bug-label.ts +13 -0
  66. package/scripts/issue/issue-classification.ts +74 -0
  67. package/scripts/issue/issue-lint-cli.ts +10 -3
  68. package/scripts/issue/markdown-section.ts +38 -4
  69. package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +27 -1
  70. package/scripts/josh/josh-command-map.ts +3 -0
  71. package/scripts/josh/josh-commands-development.ts +6 -0
  72. package/scripts/josh/josh-commands-project.ts +14 -1
  73. package/scripts/josh/josh-logic.ts +13 -0
  74. package/scripts/josh/run-github-prerequisite.ts +32 -0
  75. package/scripts/lane/lane-await.ts +8 -1
  76. package/scripts/lane/openai-lane-ship.ts +30 -0
  77. package/scripts/lane/openai-lane-supervisor.ts +35 -8
  78. package/scripts/lane/openai-review-broker.ts +260 -0
  79. package/scripts/lint/cspell-cached.ts +21 -0
  80. package/scripts/lint/lint-parallel.ts +44 -1
  81. package/scripts/observations/observations-flush.ts +1 -1
  82. package/scripts/package/production-dependency-graph.ts +68 -0
  83. package/scripts/package/verify-optional-eslint-install.ts +62 -0
  84. package/scripts/propagate/propagate-run.ts +6 -0
  85. package/scripts/propagate/propagate-steps.ts +2 -0
  86. package/scripts/release/github-release-cli.ts +11 -0
  87. package/scripts/release/github-release.ts +220 -0
  88. package/scripts/release/publish-tag-cli.ts +8 -0
  89. package/scripts/release/publish-tag.ts +11 -0
  90. package/scripts/release/release-publish.ts +7 -2
  91. package/scripts/rules/delivered-rules.ts +2 -0
  92. package/scripts/rules/issue-bug-label.ts +85 -0
  93. package/scripts/rules/issue-filing-args.ts +50 -0
  94. package/scripts/run/run-merge-cli.ts +19 -0
  95. package/scripts/run/run-merge.ts +9 -1
  96. package/scripts/run/run-ship-cli.ts +20 -0
  97. package/scripts/run/run-ship-detach.ts +180 -14
  98. package/scripts/run/run-ship-review-steps.ts +45 -25
  99. package/scripts/run/run-wake-session.ts +3 -1
  100. package/scripts/test/pilot-files.ts +0 -2
  101. package/scripts/test/skip-marker.ts +3 -0
  102. package/scripts/test/test-declared-changed.ts +2 -1
  103. package/scripts/test/test-declared-logic.ts +15 -10
  104. package/scripts/test/test-declared.ts +20 -8
  105. package/scripts/test/test-red-logic.ts +3 -3
  106. package/scripts/test/test-unit-guard.ts +24 -12
  107. package/scripts/version/version-check-arguments.ts +14 -0
  108. package/scripts/version/version-check.ts +10 -3
  109. package/templates/CLAUDE.static.md +8 -0
  110. package/templates/cursorrules.static +1 -0
  111. package/templates/prettierignore.static +12 -0
  112. package/templates/workflows/ci.yml +5 -0
package/dist/josh.js CHANGED
@@ -155,8 +155,8 @@ var doctor_consumer = {
155
155
  };
156
156
 
157
157
  // scripts/josh/josh-logic.ts
158
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
159
- import path3 from "node:path";
158
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
159
+ import path4 from "node:path";
160
160
  import { fileURLToPath } from "node:url";
161
161
 
162
162
  // scripts/build/local-bin.ts
@@ -225,7 +225,7 @@ function resolve_package_bin(base_directory, package_name, bin_name) {
225
225
  }
226
226
 
227
227
  // scripts/josh/josh-logic.ts
228
- import { execaSync as execaSync2 } from "execa";
228
+ import { execaSync as execaSync3 } from "execa";
229
229
 
230
230
  // scripts/josh/command-suggest.ts
231
231
  var MAX_SUGGESTION_DISTANCE = 3;
@@ -841,6 +841,12 @@ var FILTER_ARGUMENTS = "[filters...]";
841
841
  var MATCH_ARGUMENTS = "[--match]";
842
842
  var PATH_ARGUMENTS = "<path...>";
843
843
  var DEV_COMMANDS = {
844
+ "pr:classification": {
845
+ script: "scripts/ci/pr-classification.ts",
846
+ description: "Require one release classification on a pull request",
847
+ category: "Development",
848
+ reference: ["", "automation", ["none"]]
849
+ },
844
850
  [GATE_COMMAND]: {
845
851
  script: "scripts/gate/verification-gate.ts",
846
852
  description: "Run lint, type check, spell check and unit tests concurrently",
@@ -1160,12 +1166,25 @@ var MAINTENANCE_COMMANDS = {
1160
1166
  };
1161
1167
 
1162
1168
  // scripts/josh/josh-commands-project.ts
1169
+ var PROFILE_ARGUMENTS = "[--profile static|node]";
1163
1170
  var PROJECT_COMMANDS = {
1171
+ profile: {
1172
+ script: "scripts/init/project-profile-cli.ts",
1173
+ description: "Show the project profile and the reason for it",
1174
+ category: "Project",
1175
+ reference: [PROFILE_ARGUMENTS, "developer", ["files"]]
1176
+ },
1177
+ start: {
1178
+ script: "scripts/init/start.ts",
1179
+ description: "Initialize a project for the GitHub Issue workflow",
1180
+ category: "Project",
1181
+ reference: [PROFILE_ARGUMENTS, "developer", ["files", "git", "network"]]
1182
+ },
1164
1183
  init: {
1165
1184
  script: "scripts/init/init.ts",
1166
1185
  description: "Initialize config in a new project",
1167
1186
  category: "Project",
1168
- reference: ["", "developer", ["files", "processes"]]
1187
+ reference: [PROFILE_ARGUMENTS, "developer", ["files", "processes"]]
1169
1188
  },
1170
1189
  sync: {
1171
1190
  script: "scripts/sync/sync.ts",
@@ -1408,11 +1427,14 @@ var ALIASES = {
1408
1427
  c: "check",
1409
1428
  pt: "port",
1410
1429
  i: "init",
1430
+ pf: "profile",
1431
+ st: "start",
1411
1432
  sy: "sync",
1412
1433
  rmi: "registry:migrate",
1413
1434
  sys: "sync:scope",
1414
1435
  g: "git",
1415
1436
  gp: "pr",
1437
+ prc: "pr:classification",
1416
1438
  fu: "followup",
1417
1439
  nf: "notify",
1418
1440
  obf: "observations:flush",
@@ -1608,6 +1630,118 @@ var kit_only = {
1608
1630
  notice: kit_only_notice
1609
1631
  };
1610
1632
 
1633
+ // scripts/josh/run-github-prerequisite.ts
1634
+ import { existsSync as existsSync3 } from "node:fs";
1635
+ import path3 from "node:path";
1636
+
1637
+ // scripts/discovery/repo-origin.ts
1638
+ var GITHUB_HOST = "github.com";
1639
+ var SSH_ALIAS_PATTERN = /^github(?:-\w+)*$/u;
1640
+ var GIT_SUFFIX = ".git";
1641
+ var PATH_SEPARATOR2 = "/";
1642
+ var USER_SEPARATOR = "@";
1643
+ var HOST_SEPARATOR = ":";
1644
+ var OWNER_INDEX = 0;
1645
+ var REPO_INDEX = 1;
1646
+ var REPO_PATH_SEGMENTS = 2;
1647
+ var NOT_FOUND = -1;
1648
+ var SECTION_PREFIX = "[";
1649
+ var ORIGIN_SECTION = /^\[remote\s+"origin"\]$/u;
1650
+ var URL_KEY = "url";
1651
+ function is_github_hostname(host) {
1652
+ return host.toLowerCase() === GITHUB_HOST;
1653
+ }
1654
+ function is_github_ssh_host(host) {
1655
+ const normalized = host.toLowerCase();
1656
+ return is_github_hostname(normalized) || SSH_ALIAS_PATTERN.test(normalized);
1657
+ }
1658
+ function strip_git_suffix(name) {
1659
+ return name.endsWith(GIT_SUFFIX) ? name.slice(0, -GIT_SUFFIX.length) : name;
1660
+ }
1661
+ function to_identity(repository_path) {
1662
+ const segments = repository_path.split(PATH_SEPARATOR2).filter((segment) => segment !== "");
1663
+ if (segments.length !== REPO_PATH_SEGMENTS) return void 0;
1664
+ const owner = segments[OWNER_INDEX] ?? "";
1665
+ const repo = strip_git_suffix(segments[REPO_INDEX] ?? "");
1666
+ return repo === "" ? void 0 : { owner, repo };
1667
+ }
1668
+ function parse_scp_like(url) {
1669
+ const user_end = url.indexOf(USER_SEPARATOR);
1670
+ const authority = user_end === NOT_FOUND ? url : url.slice(user_end + 1);
1671
+ const host_end = authority.indexOf(HOST_SEPARATOR);
1672
+ if (host_end === NOT_FOUND) return void 0;
1673
+ if (!is_github_ssh_host(authority.slice(0, host_end))) return void 0;
1674
+ return to_identity(authority.slice(host_end + 1));
1675
+ }
1676
+ function parse_url_like(url) {
1677
+ try {
1678
+ const parsed = new URL(url);
1679
+ return is_github_hostname(parsed.hostname) ? to_identity(parsed.pathname) : void 0;
1680
+ } catch {
1681
+ return void 0;
1682
+ }
1683
+ }
1684
+ function parse_origin_url(url) {
1685
+ const trimmed = url.trim();
1686
+ if (trimmed === "") return void 0;
1687
+ return parse_url_like(trimmed) ?? parse_scp_like(trimmed);
1688
+ }
1689
+ function is_section_header(line) {
1690
+ return line.startsWith(SECTION_PREFIX);
1691
+ }
1692
+ function origin_section_lines(lines) {
1693
+ const start = lines.findIndex((line) => ORIGIN_SECTION.test(line));
1694
+ if (start === NOT_FOUND) return [];
1695
+ const rest = lines.slice(start + 1);
1696
+ const end = rest.findIndex((line) => is_section_header(line));
1697
+ return end === NOT_FOUND ? rest : rest.slice(0, end);
1698
+ }
1699
+ function entry_value(line, key) {
1700
+ const separator = line.indexOf("=");
1701
+ if (separator === NOT_FOUND) return void 0;
1702
+ if (line.slice(0, separator).trim() !== key) return void 0;
1703
+ return line.slice(separator + 1).trim();
1704
+ }
1705
+ function parse_origin_from_config(content) {
1706
+ const lines = content.split("\n").map((line) => line.trim());
1707
+ return origin_section_lines(lines).map((line) => entry_value(line, URL_KEY)).find((url) => url !== void 0);
1708
+ }
1709
+ function format_identity(identity) {
1710
+ return `${identity.owner}${PATH_SEPARATOR2}${identity.repo}`.toLowerCase();
1711
+ }
1712
+ var repo_origin = {
1713
+ GITHUB_HOST,
1714
+ is_github_hostname,
1715
+ is_github_ssh_host,
1716
+ parse_origin_from_config,
1717
+ parse_origin_url,
1718
+ format_identity
1719
+ };
1720
+
1721
+ // scripts/josh/run-github-prerequisite.ts
1722
+ import { execaSync as execaSync2 } from "execa";
1723
+ var GITHUB_RUN_COMMANDS = /* @__PURE__ */ new Set([
1724
+ "run:entry",
1725
+ "run:prep",
1726
+ "run:next",
1727
+ "run:step",
1728
+ "run:merge",
1729
+ "run:tail"
1730
+ ]);
1731
+ function explanation(root, command) {
1732
+ if (!GITHUB_RUN_COMMANDS.has(command)) return void 0;
1733
+ if (!existsSync3(path3.join(root, ".git"))) {
1734
+ return `${command} requires Git and a GitHub origin. Run git init and add a GitHub origin first.`;
1735
+ }
1736
+ const result = execaSync2("git", ["config", "--get", "remote.origin.url"], {
1737
+ cwd: root,
1738
+ reject: false
1739
+ });
1740
+ if (result.exitCode === 0 && repo_origin.parse_origin_url(result.stdout)) return void 0;
1741
+ return `${command} requires a GitHub origin. Add an origin pointing to GitHub first.`;
1742
+ }
1743
+ var run_github_prerequisite = { explanation };
1744
+
1611
1745
  // scripts/josh/josh-logic.ts
1612
1746
  var COLUMN_WIDTH = 26;
1613
1747
  var ALIAS_PAD_WIDTH = 2;
@@ -1617,20 +1751,20 @@ var PACKAGE_JSON2 = "package.json";
1617
1751
  var UNKNOWN_COMMAND_EXIT_CODE = -1;
1618
1752
  function find_package_directory(start_directory) {
1619
1753
  let current = start_directory;
1620
- while (!existsSync3(path3.join(current, PACKAGE_JSON2))) {
1621
- const parent = path3.dirname(current);
1754
+ while (!existsSync4(path4.join(current, PACKAGE_JSON2))) {
1755
+ const parent = path4.dirname(current);
1622
1756
  if (parent === current) return start_directory;
1623
1757
  current = parent;
1624
1758
  }
1625
1759
  return current;
1626
1760
  }
1627
- var PACKAGE_DIR = find_package_directory(path3.dirname(fileURLToPath(import.meta.url)));
1761
+ var PACKAGE_DIR = find_package_directory(path4.dirname(fileURLToPath(import.meta.url)));
1628
1762
  function resolve_tsx_executable() {
1629
1763
  const candidates = [
1630
1764
  resolve_local_bin(PACKAGE_DIR, TSX_BIN),
1631
1765
  resolve_local_bin(process.cwd(), TSX_BIN)
1632
1766
  ];
1633
- return candidates.find(existsSync3) ?? TSX_BIN;
1767
+ return candidates.find(existsSync4) ?? TSX_BIN;
1634
1768
  }
1635
1769
  function resolve_tsx_cli_entry() {
1636
1770
  for (const base_directory of [PACKAGE_DIR, process.cwd()]) {
@@ -1647,7 +1781,7 @@ function resolve_tsx_runner() {
1647
1781
  return { executable: resolve_tsx_executable(), leading_arguments: [] };
1648
1782
  }
1649
1783
  function read_package_version() {
1650
- const raw = readFileSync3(path3.join(PACKAGE_DIR, PACKAGE_JSON2), "utf8");
1784
+ const raw = readFileSync3(path4.join(PACKAGE_DIR, PACKAGE_JSON2), "utf8");
1651
1785
  return package_version_schema.parse(JSON.parse(raw)).version;
1652
1786
  }
1653
1787
  var HEADER = `josh v${read_package_version()} \u2014 Joshua Folkken's dev toolkit`;
@@ -1706,7 +1840,7 @@ function should_use_shell(executable) {
1706
1840
  return process.platform === "win32" && executable !== process.execPath;
1707
1841
  }
1708
1842
  function spawn_script(tsx_executable, script_arguments) {
1709
- const result = execaSync2(tsx_executable, script_arguments, {
1843
+ const result = execaSync3(tsx_executable, script_arguments, {
1710
1844
  stdio: "inherit",
1711
1845
  shell: should_use_shell(tsx_executable),
1712
1846
  reject: false
@@ -1715,7 +1849,7 @@ function spawn_script(tsx_executable, script_arguments) {
1715
1849
  }
1716
1850
  function run_shell_command(shell, extra) {
1717
1851
  const [executable = "", ...rest_arguments] = shell;
1718
- const result = execaSync2(executable, [...rest_arguments, ...extra], {
1852
+ const result = execaSync3(executable, [...rest_arguments, ...extra], {
1719
1853
  stdio: "inherit",
1720
1854
  reject: false
1721
1855
  });
@@ -1731,18 +1865,27 @@ function spawn_script_entry(entry, script_path, script_arguments) {
1731
1865
  ]);
1732
1866
  }
1733
1867
  async function run_script_entry(entry, subcommand_arguments) {
1734
- const script_path = path3.join(PACKAGE_DIR, entry.script ?? "");
1868
+ const script_path = path4.join(PACKAGE_DIR, entry.script ?? "");
1735
1869
  const script_arguments = [...entry.default_script_arguments ?? [], ...subcommand_arguments];
1736
1870
  if (josh_in_process.can_run_in_process(entry, import.meta.url)) {
1737
1871
  return await josh_in_process.run_in_process(script_path, script_arguments);
1738
1872
  }
1739
1873
  return spawn_script_entry(entry, script_path, script_arguments);
1740
1874
  }
1875
+ function github_prerequisite_exit(resolved, is_consumer) {
1876
+ if (!is_consumer) return void 0;
1877
+ const prerequisite = run_github_prerequisite.explanation(process.cwd(), resolved);
1878
+ if (prerequisite === void 0) return void 0;
1879
+ console.error(prerequisite);
1880
+ return USAGE_ERROR_EXIT_CODE;
1881
+ }
1741
1882
  function pre_dispatch_exit(resolved, entry, subcommand_arguments, is_consumer) {
1742
1883
  if (is_consumer && kit_only.is_kit_only(entry)) {
1743
1884
  console.error(kit_only.notice(resolved));
1744
1885
  return USAGE_ERROR_EXIT_CODE;
1745
1886
  }
1887
+ const prerequisite_exit = github_prerequisite_exit(resolved, is_consumer);
1888
+ if (prerequisite_exit !== void 0) return prerequisite_exit;
1746
1889
  const rejection = composite_arguments.reject_extra_arguments(
1747
1890
  resolved,
1748
1891
  entry,
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.883.0",
2
+ "version": "1.921.0",
3
3
  "name": "@joshuafolkken/kit",
4
4
  "type": "module",
5
5
  "license": "MIT",
@@ -82,6 +82,10 @@
82
82
  "types": "./prettier/index.d.ts",
83
83
  "default": "./prettier/index.js"
84
84
  },
85
+ "./prettier/static": {
86
+ "types": "./prettier/static.d.ts",
87
+ "default": "./prettier/static.js"
88
+ },
85
89
  "./tsconfig/base": {
86
90
  "types": "./tsconfig/base.json",
87
91
  "default": "./tsconfig/base.json"
@@ -106,56 +110,103 @@
106
110
  },
107
111
  "peerDependencies": {
108
112
  "@playwright/test": ">=1.0.0",
109
- "eslint": ">=9.18.0",
110
- "svelte": ">=5.0.0"
113
+ "@eslint/compat": "^2.1.1",
114
+ "@eslint/js": "^10.0.1",
115
+ "@stylistic/eslint-plugin": "^5.10.0",
116
+ "eslint": "^10.0.0",
117
+ "eslint-config-prettier": "^10.1.8",
118
+ "eslint-import-resolver-typescript": "^4.4.5",
119
+ "eslint-plugin-import-x": "^4.17.1",
120
+ "eslint-plugin-promise": "^7.3.0",
121
+ "eslint-plugin-sonarjs": "^4.2.1",
122
+ "eslint-plugin-unicorn": "^76.0.0",
123
+ "globals": "^17.12.0",
124
+ "typescript": "^6.0.3",
125
+ "typescript-eslint": "^8.70.1"
111
126
  },
112
127
  "peerDependenciesMeta": {
113
128
  "@playwright/test": {
114
129
  "optional": true
115
130
  },
116
- "svelte": {
131
+ "@eslint/compat": {
132
+ "optional": true
133
+ },
134
+ "@eslint/js": {
135
+ "optional": true
136
+ },
137
+ "@stylistic/eslint-plugin": {
138
+ "optional": true
139
+ },
140
+ "eslint": {
141
+ "optional": true
142
+ },
143
+ "eslint-config-prettier": {
144
+ "optional": true
145
+ },
146
+ "eslint-import-resolver-typescript": {
147
+ "optional": true
148
+ },
149
+ "eslint-plugin-import-x": {
150
+ "optional": true
151
+ },
152
+ "eslint-plugin-promise": {
153
+ "optional": true
154
+ },
155
+ "eslint-plugin-sonarjs": {
156
+ "optional": true
157
+ },
158
+ "eslint-plugin-unicorn": {
159
+ "optional": true
160
+ },
161
+ "globals": {
162
+ "optional": true
163
+ },
164
+ "typescript": {
165
+ "optional": true
166
+ },
167
+ "typescript-eslint": {
117
168
  "optional": true
118
169
  }
119
170
  },
120
171
  "devDependencies": {
121
172
  "@actions/expressions": "^0.3.61",
173
+ "@eslint/compat": "^2.1.1",
174
+ "@eslint/js": "^10.0.1",
122
175
  "@ianvs/prettier-plugin-sort-imports": "^4.7.1",
123
- "@secretlint/secretlint-rule-preset-recommend": "^13.0.5",
176
+ "@secretlint/secretlint-rule-preset-recommend": "^13.0.6",
177
+ "@stylistic/eslint-plugin": "^5.10.0",
124
178
  "@types/node": "^26.6.3",
125
179
  "@types/semver": "^7.8.0",
126
180
  "cspell": "^10.3.4",
127
181
  "esbuild": "^0.28.2",
128
182
  "eslint": "^10.11.0",
183
+ "eslint-config-prettier": "^10.1.8",
184
+ "eslint-import-resolver-typescript": "^4.4.5",
185
+ "eslint-plugin-import-x": "^4.17.1",
186
+ "eslint-plugin-promise": "^7.3.0",
187
+ "eslint-plugin-sonarjs": "^4.2.1",
188
+ "eslint-plugin-unicorn": "^76.0.0",
189
+ "eslint_d": "^15.0.3",
190
+ "globals": "^17.12.0",
129
191
  "lefthook": "^2.1.14",
130
192
  "prettier": "^3.9.9",
131
193
  "prettier-plugin-svelte": "^4.1.1",
132
194
  "prettier-plugin-tailwindcss": "^0.8.1",
133
195
  "rollup": "^4.63.5",
134
196
  "rollup-plugin-dts": "^6.5.1",
135
- "secretlint": "^13.0.5",
197
+ "secretlint": "^13.0.6",
136
198
  "typescript": "^6.0.3",
137
- "vitest": "^5.0.1"
199
+ "vitest": "^5.0.2",
200
+ "typescript-eslint": "^8.70.1"
138
201
  },
139
202
  "dependencies": {
140
- "@eslint/compat": "^2.1.1",
141
- "@eslint/js": "^10.0.1",
142
- "@stylistic/eslint-plugin": "^5.10.0",
143
- "eslint-config-prettier": "^10.1.8",
144
- "eslint-import-resolver-typescript": "^4.4.5",
145
- "eslint-plugin-import-x": "^4.17.1",
146
- "eslint-plugin-promise": "^7.3.0",
147
- "eslint-plugin-sonarjs": "^4.2.1",
148
- "eslint-plugin-unicorn": "^76.0.0",
149
- "eslint_d": "^15.0.3",
150
203
  "execa": "^10.0.1",
151
- "globals": "^17.12.0",
152
204
  "js-yaml": "^5.4.2",
153
205
  "jsonc-parser": "^3.3.1",
154
206
  "ora": "^9.4.1",
155
207
  "semver": "^7.8.5",
156
208
  "strip-json-comments": "^5.0.3",
157
209
  "tsx": "^4.23.15",
158
- "typescript-eslint": "^8.70.1",
159
210
  "zod": "^4.6.5"
160
211
  },
161
212
  "devEngines": {
package/prettier/index.js CHANGED
@@ -1,12 +1,7 @@
1
+ import { config as static_config } from './static.js'
2
+
1
3
  export const config = {
2
- useTabs: true,
3
- singleQuote: true,
4
- trailingComma: 'all',
5
- printWidth: 100,
6
- semi: false,
7
- arrowParens: 'always',
8
- bracketSpacing: true,
9
- endOfLine: 'lf',
4
+ ...static_config,
10
5
  plugins: [
11
6
  '@ianvs/prettier-plugin-sort-imports',
12
7
  'prettier-plugin-svelte',
@@ -0,0 +1,3 @@
1
+ import type { Config } from 'prettier'
2
+
3
+ export const config: Config
@@ -0,0 +1,10 @@
1
+ export const config = {
2
+ useTabs: true,
3
+ singleQuote: true,
4
+ trailingComma: 'all',
5
+ printWidth: 100,
6
+ semi: false,
7
+ arrowParens: 'always',
8
+ bracketSpacing: true,
9
+ endOfLine: 'lf',
10
+ }
@@ -13,6 +13,8 @@ Issue には次の要素を必ず含める。
13
13
  ````md
14
14
  ## 背景
15
15
 
16
+ <不具合なら `- 種別: 不具合`、そうでなければ `- 種別: 非不具合` を宣言する>
17
+
16
18
  <なぜ必要か>
17
19
 
18
20
  ## 現象
@@ -57,7 +59,7 @@ Issue には次の要素を必ず含める。
57
59
 
58
60
  **AI の振る舞いを変えると主張する Issue は、`## 発火点` と `## ベースライン` と `## 再現` の 3 見出しを必ず含める。** 散文の規則は「配って終わり」になりやすく、効いたかどうかがマージ後に読み直されないためである(joshuafolkken/kit#2178)。読みだけを根拠にした起票(既に直った現象の起票・壊れたままの現象の誤読)を防ぐため、再現の実測も課す(joshuafolkken/kit#2353)。
59
61
 
60
- - **対象かどうかは本文の宣言から読み、判断させない。** 本文に `- 種別: 振る舞い変更` の行がある Issue だけが対象で、コードだけを変える Issue(`- 種別: コードのみ`、または宣言なし)には 3 見出しを課さない。宣言は `## 背景` の直下に置く
62
+ - **対象かどうかは本文の宣言から読み、判断させない。** 本文に `- 種別: 振る舞い変更` の行がある Issue だけが対象で、コードだけを変える Issue(`- 種別: コードのみ`、または振る舞い変更の宣言なし)には 3 見出しを課さない。宣言は `## 背景` の直下に置く
61
63
 
62
64
  ```md
63
65
  ## 背景
@@ -67,11 +69,16 @@ Issue には次の要素を必ず含める。
67
69
  <なぜ必要か>
68
70
  ```
69
71
 
70
- - **不具合修正の Issue は `- 種別: 不具合` を宣言する。** 同じ位置(`## 背景` の直下)に置く。この宣言がある Issue では、`pnpm josh git -y` が追加・変更したテストを修正前のツリー(merge-base)に当てて `pnpm josh test:red` で実行し、1 本も赤にならない(`green`)ときはコミットを拒否する。修正前でも緑の回帰テストは、報告された症状を再現していないためである(joshuafolkken/kit#2448)。回帰テストは、利用者が症状を見たのと同じ粒度で書く
72
+ - **起票前に不具合かどうかを必ず選ぶ。** `## 背景` の直下に、不具合なら `- 種別: 不具合`、非不具合なら `- 種別: 非不具合` を置く。両方の併記も省略も `pnpm josh issue:lint` が拒否する。故障を述べる本文でも単語から自動判定せず、起票者が判断する。振る舞い変更、機能改善、互換性の宣言はこの判断と併記できる
73
+ - **不具合修正の Issue は `- 種別: 不具合` を宣言する。** この宣言がある Issue では、`pnpm josh git -y` が追加・変更したテストを修正前のツリー(merge-base)に当てて `pnpm josh test:red` で実行し、1 本も赤にならない(`green`)ときはコミットを拒否する。修正前でも緑の回帰テストは、報告された症状を再現していないためである(joshuafolkken/kit#2448)。回帰テストは、利用者が症状を見たのと同じ粒度で書く
74
+ - **起票時の `bug` ラベルは本文の宣言から決める。** `pnpm josh issue:lint <本文ファイル>` の後、同じファイルを `gh api …/issues -F body=@<本文ファイル>` で送る。`labels:` に `bug` が含まれたら `-f 'labels[]=bug'` を同じ作成呼び出しに追加する。`depth`・`route` は併記する。`bugfix` は PR のリリース分類であり、Issue の `bug` の代わりにしない
75
+ - **機能追加・機能改善は目的を本文で宣言する。** `## 背景` の直下に `- 目的: 機能追加` または `- 目的: 機能改善` を書く。`pnpm josh issue:lint <本文ファイル>` が `labels: enhancement` を返したら、同じ作成呼び出しに `-f 'labels[]=enhancement'` を付ける。不具合修正、文書のみ、依存更新、保守作業はタイトルの単語から推測して宣言しない。目的が曖昧なら起票文案の期待結果と受け入れ条件を具体化してから分類する
76
+ - **互換性を壊す変更は影響を本文で宣言する。** 既存の利用者、API、設定の契約を変える場合は、`## 背景` の直下に `- 互換性: 破壊的変更` を書き、廃止対象と影響を説明する。移行措置や事前案内があっても破壊的変更なら宣言する。`pnpm josh issue:lint <本文ファイル>` が `labels: breaking-change` を返したら、同じ作成呼び出しに `-f 'labels[]=breaking-change'` を付ける。互換性を保つ変更には付けない。影響が曖昧なら起票文案で対象の利用者と契約を明確にしてから分類する
77
+ - **`issue:lint` が返す分類ラベルをすべて起票時に指定する。** `bug`、`enhancement`、`breaking-change` は組み合わせて表示されることがある。`depth`・`route` など既存のラベルを残し、`labels: none` のときは分類ラベルを付けない。Issue が `enhancement` と `breaking-change` の両方を持つとき、PR のリリース分類は `breaking-change` 一つにする。Issue の目的の分類と PR のリリース分類を区別する
71
78
  - **`## 発火点`** には、その規則が破れる瞬間に最初に呼ばれるツールコール名を 1 つ書く。名前は `prompts/collaboration-workflow/rule-delivery.md` の配送表に載る、フックで配送可能なツール(`Bash` / `Edit` / `Read` / `Write` / `AskUserQuestion`)と突き合わせる。**表に無い名前**(そもそもツールコール名でない)と**不一致**(既知のツールだが配送できない)は区別して印字され、joshuafolkken/kit#2201 の「規則は正しいが名指ししたコールが実際に破れるコールの 1 つ後ろ」を起票時に検出する
72
79
  - **`## ベースライン`** は **「コマンド + 値」** で書く。``- `<測定コマンド>` → <現在の値>`` の形に限り、自然文だけの測定説明は受け付けない(再実行できないため)。マージ後に `pnpm josh measure:rerun` が同じコマンドをやり直し、**before / after の対**を印字する。値が動かなかったとき、反証された前提として `docs/observations.md` に 1 行追記される(既存の append-only 台帳をそのまま使い、第 2 の台帳は作らない)
73
80
  - **`## 再現`** は **「コマンド + 実際の出力」** で書く。コマンドを backtick、出力をフェンス(``` または ~~~)で囲む。自然文の「確認した」は再実行できないため受け付けない(`## ベースライン` と同じ理由)。読みだけの起票を起票時に弾くためである
74
- - **検査は `pnpm josh issue:lint <path>` が行う。** 対象判定・3 見出しの有無・発火点の配送可否・ベースラインが「コマンド + 値」か・再現が「コマンド + 実際の出力」かを、起票前の本文ファイルにまとめて検査する(未リントの起票は `pnpm josh rule:guard` が拒否する)
81
+ - **検査は `pnpm josh issue:lint <path>` が行う。** 対象判定・3 見出しの有無・発火点の配送可否・ベースラインが「コマンド + 値」か・再現が「コマンド + 実際の出力」かと、起票時に必要な分類ラベルを、起票前の本文ファイルからまとめて答える(未リントの起票は `pnpm josh rule:guard` が拒否する)
75
82
 
76
83
  ### 起票元へのバックリンク(`## Origin` / `## Upstream issues`)
77
84
 
@@ -130,7 +137,7 @@ pnpm josh epic "<epic-title>" <N1> <N2> --origin <owner/repo#N>
130
137
 
131
138
  1. 子 Issue を全て作成し、番号 `<N1> <N2> ...` を控える
132
139
  2. `epic` ラベルを用意する(未作成なら `gh api repos/{owner}/{repo}/labels -f name=epic -f color=5319e7 -f description="Tracks a batch of child issues from one split" --silent 2>/dev/null || true`)
133
- 3. epic を作成し、番号 `<E>` を控える: `gh api repos/{owner}/{repo}/issues -f title="<epic-title>" -f 'labels[]=epic' -f body="<body>" --jq .number`(`--jq` を落とすと Issue の JSON 全体が出る。そこには `id` も含まれるが、**控えるのは `number` である** — `id` はデータベース id で、手順 4 が要求する値ではあっても epic の番号ではない)
140
+ 3. epic を作成し、番号 `<E>` を控える: `gh api repos/{owner}/{repo}/issues -f title="<epic-title>" -f 'labels[]=epic' -F body=@<body-file> --jq .number`(`--jq` を落とすと Issue の JSON 全体が出る。そこには `id` も含まれるが、**控えるのは `number` である** — `id` はデータベース id で、手順 4 が要求する値ではあっても epic の番号ではない)
134
141
  4. 実行順序が**ある場合のみ**、子 Issue に依存関係を付与する(後述の理由により**作成後の独立ステップ**として行う):
135
142
 
136
143
  ```bash
@@ -16,7 +16,7 @@
16
16
 
17
17
  1. **現在の作業を退避する**: `git stash push -u -m "upstream-interrupt: paused #<N>"`(WIP コミットでも可)。退避したことを忘れないよう、この時点で stash を作る。メッセージ付きで積むのは、復元をメッセージで特定するため(下記ステップ 5)
18
18
  2. **進行中の Issue に状況を明記する**: `pnpm josh issue:comment <N> --body-file <path>` で、(a) 作業を stash したこと、(b) 中断理由(どの別パッケージの・どんな問題で中断したか)、(c) 対象パッケージに作成した新 Issue へのリンクを `## Upstream issues` 見出しの下に `owner/repo#N` 形式で、を記載する。これにより「なぜこの Issue が一時停止しているか」と「何を待っているのか」が後から監査できる(→「起票元へのバックリンク」)
19
- 3. **対象パッケージのリポジトリに新しい Issue を作成する(確認なし)**: `gh api repos/<owner>/<repo>/issues -f title="<root-cause title>" -f 'labels[]=route:tier-a' -f 'labels[]=depth:<n>' -f body="<root cause and context>"`(`route:tier-a` は実装中の Tier A 起票を経路として集計するためのラベル — joshuafolkken/kit#1083。付与するのは first-party の場合だけで、third-party はそもそもこの手順に到達せず起票しない)。根本原因・再現・期待結果を Step 1 のテンプレに沿って記載し、**本文に `## Origin` 節を置いて起票元 Issue を `owner/repo#N` 形式で書く**(→「起票元へのバックリンク」)。上流 Issue は欠陥を、起票元 Issue は証拠を持つため、リンクがないと上流 Issue は後から解釈できなくなる。ここで「起票してよいか」を尋ねて停止してはならない。**ただし確認なしで起票してよいのは対象が first-party のときだけ**で、third-party(owner が自リポジトリと一致しないリポジトリ)なら起票せずに停止する(→「第三者リポジトリへの書き込みは Tier C(明示指示が必要)」)
19
+ 3. **対象パッケージのリポジトリに新しい Issue を作成する(確認なし)**: `gh api repos/<owner>/<repo>/issues -f title="<root-cause title>" -f 'labels[]=route:tier-a' -f 'labels[]=depth:<n>' -F body=@<body-file>`(`route:tier-a` は実装中の Tier A 起票を経路として集計するためのラベル — joshuafolkken/kit#1083。付与するのは first-party の場合だけで、third-party はそもそもこの手順に到達せず起票しない)。起票前に `pnpm josh issue:lint <body-file>` を実行し、`labels:` に列挙された分類ラベルをすべて同じ作成呼び出しに付ける。分類の単一ソースは `prompts/collaboration-workflow/issue-template.md`。根本原因・再現・期待結果を Step 1 のテンプレに沿って記載し、**本文に `## Origin` 節を置いて起票元 Issue を `owner/repo#N` 形式で書く**(→「起票元へのバックリンク」)。上流 Issue は欠陥を、起票元 Issue は証拠を持つため、リンクがないと上流 Issue は後から解釈できなくなる。ここで「起票してよいか」を尋ねて停止してはならない。**ただし確認なしで起票してよいのは対象が first-party のときだけ**で、third-party(owner が自リポジトリと一致しないリポジトリ)なら起票せずに停止する(→「第三者リポジトリへの書き込みは Tier C(明示指示が必要)」)
20
20
  4. **`confirmation` Telegram を送って停止する**: 上流 Issue の URL と、何が止まっているかを本文に書く(→「確認待ちで停止するときの Telegram 通知(`confirmation`)」)。無人実行でも画面外でユーザーが気付ける。停止は Issue が既に存在する状態で行うので、ユーザーは「待つ / 先送りする」を Issue を見ながら 1 語で答えられる
21
21
  5. **元の作業を再開する**: 上流の修正がマージされた、または**ユーザーが先送りを明示判断した**後に `pnpm josh stash:pop "upstream-interrupt: paused #<N>"`(位置指定や引数なしの `git stash pop` は使わない — stash は全 work tree が共有する 1 本のスタックで、別のレーンの stash を取り込む恐れがある)して、退避していた元タスクを続行する。上流 Issue を割り込みで実装するかどうかもユーザーの判断(上流パッケージの実装・PR・マージはそれぞれのワークフロー規則に従う)
22
22
 
@@ -69,10 +69,10 @@ gh api "repos/{owner}/{repo}/issues?state=open&per_page=100" --paginate \
69
69
 
70
70
  #### 該当したときの手順
71
71
 
72
- 1. **上限に関係なく起票する。** 1 件閉じることを条件にしない。
72
+ 1. **上限に関係なく起票する。** 1 件閉じることを条件にしない。起票前に `pnpm josh issue:lint <body-file>` を実行し、その `labels:` に列挙された分類ラベルをすべて同じ作成呼び出しに付ける。`prompts/collaboration-workflow/issue-template.md` の手順に従う。
73
73
 
74
74
  ```bash
75
- gh api repos/{owner}/{repo}/issues -f title="<title>" -f 'labels[]=route:interrupt' -f 'labels[]=depth:<n>' -f body="<body>"
75
+ gh api repos/{owner}/{repo}/issues -f title="<title>" -f 'labels[]=route:interrupt' -f 'labels[]=depth:<n>' -F body=@<body-file>
76
76
  ```
77
77
 
78
78
  2. **超過している事実と、当たった条件の番号を本文に 1 行書く**(例:「オープン 37 件で起票。3 条件のうち 2(文書化された手順が完了できない)に該当」)。**どちらか一方では足りない** — 件数だけでは判定が再現できず、条件だけでは上限の可視化が効かない。
package/prompts/review.md CHANGED
@@ -194,8 +194,8 @@ Only branch 2 files an Issue. What follows applies to that branch.
194
194
  two questions decide the filing-time fold"). `pnpm josh rule:guard` refuses the second `gh api …
195
195
  issues` call until it is folded; the first filing asks nothing.
196
196
 
197
- 1. File the follow-up Issue referencing the current one, tagged `route:review-cap`:
198
- `gh api repos/{owner}/{repo}/issues -f title="<title>" -f 'labels[]=route:review-cap' -f 'labels[]=depth:<n>' -f body="<body referencing the current Issue>"`.
197
+ 1. File the follow-up Issue referencing the current one, tagged `route:review-cap`. Run `pnpm josh issue:lint <body-file>` first and apply every label in its `labels:` answer at creation, following `prompts/collaboration-workflow/issue-template.md`:
198
+ `gh api repos/{owner}/{repo}/issues -f title="<title>" -f 'labels[]=route:review-cap' -f 'labels[]=depth:<n>' -F body=@<body-file>`.
199
199
  2. Run `pnpm josh epic:bundle <new>` — **before the current Issue closes.** The candidate search reads
200
200
  open issues only, so once the parent has closed the command answers `none` permanently.
201
201
  3. Act on its answer. **`epic:bundle` recommends and writes nothing**, so acting means running the
@@ -23,6 +23,7 @@ Req N: ...
23
23
 
24
24
  - **Every user-facing behavior change** (UI interaction, keyboard shortcut, visible state change) must have a corresponding E2E test.
25
25
  - **Every logic/utility change** (pure functions, filters, transforms) must have a unit test.
26
+ - **static の HTML/CSS だけの変更:** `package.json` に `josh.profile: static` が記録されていれば自動テストは不要。ブラウザーで対象ページを開き、想定する画面幅でレイアウト・リンク・HTML 内の操作を確認して結果を記録する。同じ変更に JavaScript または TypeScript のファイルが含まれれば自動テストを行う。
26
27
  - If a test is genuinely infeasible (e.g., native OS date-picker popup cannot be driven by Playwright), write a comment in the test file explaining why and test the closest observable behavior instead (e.g., that the editor remains open after the `change` event fires).
27
28
  - Do **not** report a requirement as done if its test is missing.
28
29
 
@@ -6,8 +6,12 @@ import { init_logic } from '#scripts/init/init-logic'
6
6
 
7
7
  const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url))
8
8
  const REPO_ROOT = path.resolve(SCRIPT_DIR, '..', '..')
9
+ const DIST_ROOT = path.join(REPO_ROOT, 'dist')
10
+ const STATIC_CLAUDE_FILENAME = 'CLAUDE.static.md'
9
11
  const SOURCE_CLAUDE_MD = path.join(REPO_ROOT, 'CLAUDE.md')
10
- const DIST_CLAUDE_MD = path.join(REPO_ROOT, 'dist', 'CLAUDE.md')
12
+ const DIST_CLAUDE_MD = path.join(DIST_ROOT, 'CLAUDE.md')
13
+ const SOURCE_STATIC_CLAUDE_MD = path.join(REPO_ROOT, 'templates', STATIC_CLAUDE_FILENAME)
14
+ const DIST_STATIC_CLAUDE_MD = path.join(DIST_ROOT, STATIC_CLAUDE_FILENAME)
11
15
 
12
16
  // Read kit's own CLAUDE.md and apply the distribution path transform. kit's source keeps relative
13
17
  // paths so it resolves inside the kit repository; the published copy rewrites them so every backtick
@@ -23,6 +27,7 @@ function generate_distributed_claude_md(): string {
23
27
  function build_claude_md(): string {
24
28
  mkdirSync(path.dirname(DIST_CLAUDE_MD), { recursive: true })
25
29
  writeFileSync(DIST_CLAUDE_MD, generate_distributed_claude_md())
30
+ writeFileSync(DIST_STATIC_CLAUDE_MD, readFileSync(SOURCE_STATIC_CLAUDE_MD, 'utf8'))
26
31
 
27
32
  return DIST_CLAUDE_MD
28
33
  }