@akiojin/gwt 9.6.0 → 9.7.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/.husky/pre-push CHANGED
@@ -6,7 +6,9 @@ echo "Running CI-equivalent lint checks..."
6
6
 
7
7
  cargo clippy --all-targets --all-features -- -D warnings
8
8
  cargo fmt --all -- --check
9
- bunx --bun markdownlint-cli . --config .markdownlint.json --ignore target --ignore CHANGELOG.md
9
+ cargo llvm-cov -p gwt-core -p gwt --all-features --json --summary-only --output-path target/coverage-summary.json
10
+ node scripts/check-coverage-threshold.mjs target/coverage-summary.json 90
11
+ bunx --bun markdownlint-cli . --config .markdownlint.json --ignore target --ignore CHANGELOG.md --ignore tasks/todo.md
10
12
  pnpm lint:skills
11
13
 
12
14
  echo "All pre-push checks passed."
package/README.ja.md CHANGED
@@ -66,6 +66,9 @@ gwt board show
66
66
  gwt hook workflow-policy
67
67
  ```
68
68
 
69
+ managed hook と runtime 委譲の入口も引き続き `gwt` です。利用者が別の daemon
70
+ コマンドを手動起動する必要はありません。
71
+
69
72
  ## 基本フロー
70
73
 
71
74
  1. リポジトリを開く、または前回のプロジェクトを復元する
package/README.md CHANGED
@@ -68,6 +68,9 @@ gwt board show
68
68
  gwt hook workflow-policy
69
69
  ```
70
70
 
71
+ Managed hooks and runtime delegation continue to enter through `gwt`. There is
72
+ no separate operator-facing daemon command to start by hand.
73
+
71
74
  ## Main Workflow
72
75
 
73
76
  1. Open a repository directory or restore the previous project.
package/bin/gwt.cjs CHANGED
@@ -10,6 +10,7 @@ const path = require("path");
10
10
  const fs = require("fs");
11
11
 
12
12
  const {
13
+ bundleBinaryNamesForPlatform,
13
14
  binaryNameForPlatform,
14
15
  installReleaseBinary,
15
16
  releaseAssetUrl,
@@ -19,6 +20,7 @@ const REPO = "akiojin/gwt";
19
20
  const BIN_DIR = __dirname;
20
21
  const BIN_NAME = binaryNameForPlatform(process.platform);
21
22
  const BIN_PATH = path.join(BIN_DIR, BIN_NAME);
23
+ const BUNDLE_BINARIES = bundleBinaryNamesForPlatform(process.platform);
22
24
 
23
25
  function readVersion() {
24
26
  const pkg = path.join(__dirname, "..", "package.json");
@@ -26,24 +28,25 @@ function readVersion() {
26
28
  }
27
29
 
28
30
  async function ensureBinary() {
29
- if (fs.existsSync(BIN_PATH)) return;
31
+ if (BUNDLE_BINARIES.every((name) => fs.existsSync(path.join(BIN_DIR, name)))) {
32
+ return;
33
+ }
30
34
 
31
35
  const version = readVersion();
32
36
  const { url } = releaseAssetUrl(REPO, version, process.platform, process.arch);
33
37
 
34
- console.log(`Downloading gwt binary for ${process.platform}-${process.arch}...`);
38
+ console.log(`Downloading gwt bundle for ${process.platform}-${process.arch}...`);
35
39
  console.log(`Downloading from: ${url}`);
36
40
 
37
41
  await installReleaseBinary({
38
42
  repo: REPO,
39
43
  version,
40
44
  binDir: BIN_DIR,
41
- binaryName: BIN_NAME,
42
45
  platform: process.platform,
43
46
  arch: process.arch,
44
47
  });
45
48
 
46
- console.log("gwt binary installed successfully!");
49
+ console.log(`gwt bundle installed successfully: ${BUNDLE_BINARIES.join(", ")}`);
47
50
  }
48
51
 
49
52
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akiojin/gwt",
3
- "version": "9.6.0",
3
+ "version": "9.7.0",
4
4
  "description": "Desktop GUI for Git worktree management and coding agent launch",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ const [summaryPath, thresholdArg] = process.argv.slice(2);
7
+
8
+ if (!summaryPath || !thresholdArg) {
9
+ console.error(
10
+ "Usage: node scripts/check-coverage-threshold.mjs <summary-json> <threshold>",
11
+ );
12
+ process.exit(2);
13
+ }
14
+
15
+ const threshold = Number(thresholdArg);
16
+ if (!Number.isFinite(threshold)) {
17
+ console.error(`Invalid threshold: ${thresholdArg}`);
18
+ process.exit(2);
19
+ }
20
+
21
+ const report = JSON.parse(fs.readFileSync(summaryPath, "utf8"));
22
+ const files = report.data?.flatMap((entry) => entry.files ?? []) ?? [];
23
+ const ignoredFilePatterns = [/(^|[\\/])gwt[\\/]src[\\/]main\.rs$/i];
24
+
25
+ let coveredLines = 0;
26
+ let totalLines = 0;
27
+ const excludedFiles = [];
28
+
29
+ for (const file of files) {
30
+ const filename = file.filename ?? "";
31
+ if (ignoredFilePatterns.some((pattern) => pattern.test(filename))) {
32
+ excludedFiles.push(path.relative(process.cwd(), filename));
33
+ continue;
34
+ }
35
+
36
+ const lines = file.summary?.lines;
37
+ if (!lines) {
38
+ continue;
39
+ }
40
+
41
+ coveredLines += lines.covered;
42
+ totalLines += lines.count;
43
+ }
44
+
45
+ if (totalLines === 0) {
46
+ console.error("Coverage report did not contain any line summary data.");
47
+ process.exit(1);
48
+ }
49
+
50
+ const percent = (coveredLines / totalLines) * 100;
51
+ console.log(
52
+ `Filtered line coverage: ${percent.toFixed(2)}% (${coveredLines}/${totalLines})`,
53
+ );
54
+ if (excludedFiles.length > 0) {
55
+ console.log(`Excluded from threshold: ${excludedFiles.join(", ")}`);
56
+ }
57
+
58
+ if (percent + Number.EPSILON < threshold) {
59
+ console.error(
60
+ `Coverage threshold not met: required ${threshold.toFixed(2)}%, got ${percent.toFixed(2)}%`,
61
+ );
62
+ process.exit(1);
63
+ }
64
+
65
+ console.log(`Coverage threshold met: ${threshold.toFixed(2)}%`);
@@ -5,14 +5,14 @@ const fs = require("fs");
5
5
  const path = require("path");
6
6
 
7
7
  const {
8
- binaryNameForPlatform,
8
+ bundleBinaryNamesForPlatform,
9
9
  installReleaseBinary,
10
10
  releaseAssetUrl,
11
11
  } = require("./release-assets.cjs");
12
12
 
13
13
  const REPO = "akiojin/gwt";
14
14
  const BIN_DIR = path.join(__dirname, "..", "bin");
15
- const BINARY_NAME = binaryNameForPlatform();
15
+ const BUNDLE_BINARIES = bundleBinaryNamesForPlatform();
16
16
 
17
17
  function readVersion() {
18
18
  const pkg = path.join(__dirname, "..", "package.json");
@@ -29,7 +29,7 @@ async function main() {
29
29
  const version = readVersion();
30
30
  const { url } = releaseAssetUrl(REPO, version);
31
31
 
32
- console.log(`Downloading gwt binary for ${process.platform}-${process.arch}...`);
32
+ console.log(`Downloading gwt bundle for ${process.platform}-${process.arch}...`);
33
33
  console.log(`Downloading from: ${url}`);
34
34
 
35
35
  try {
@@ -37,9 +37,8 @@ async function main() {
37
37
  repo: REPO,
38
38
  version,
39
39
  binDir: BIN_DIR,
40
- binaryName: BINARY_NAME,
41
40
  });
42
- console.log("gwt binary installed successfully!");
41
+ console.log(`gwt bundle installed successfully: ${BUNDLE_BINARIES.join(", ")}`);
43
42
  } catch (err) {
44
43
  console.error(`gwt: failed to download binary - ${err.message}`);
45
44
  console.error("gwt: you can build from source with: cargo build --release -p gwt");
@@ -8,6 +8,14 @@ function binaryNameForPlatform(platform = os.platform()) {
8
8
  return platform === "win32" ? "gwt.exe" : "gwt";
9
9
  }
10
10
 
11
+ function daemonBinaryNameForPlatform(platform = os.platform()) {
12
+ return platform === "win32" ? "gwtd.exe" : "gwtd";
13
+ }
14
+
15
+ function bundleBinaryNamesForPlatform(platform = os.platform()) {
16
+ return [binaryNameForPlatform(platform), daemonBinaryNameForPlatform(platform)];
17
+ }
18
+
11
19
  function releaseAssetName(platform = os.platform(), arch = os.arch()) {
12
20
  if (platform === "darwin" && arch === "arm64") {
13
21
  return "gwt-macos-arm64.tar.gz";
@@ -120,16 +128,15 @@ function cleanupTempDir(tempRoot) {
120
128
  }
121
129
  }
122
130
 
123
- function installBinaryFromArchive({
131
+ function installBundleFromArchive({
124
132
  archivePath,
125
133
  asset,
126
134
  binDir,
127
- binaryName = binaryNameForPlatform(),
128
135
  platform = os.platform(),
136
+ binaryNames = bundleBinaryNamesForPlatform(platform),
129
137
  }) {
130
138
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "gwt-extract-"));
131
139
  const extractDir = path.join(tempRoot, "extract");
132
- const dest = path.join(binDir, binaryName);
133
140
 
134
141
  fs.mkdirSync(binDir, { recursive: true });
135
142
  fs.mkdirSync(extractDir, { recursive: true });
@@ -137,17 +144,22 @@ function installBinaryFromArchive({
137
144
  try {
138
145
  extractArchive(archivePath, extractDir, asset);
139
146
 
140
- const extractedBinary = findFileRecursive(extractDir, binaryName);
141
- if (!extractedBinary) {
142
- throw new Error(`Extracted archive does not contain ${binaryName}`);
143
- }
147
+ const destinations = {};
148
+ for (const binaryName of binaryNames) {
149
+ const extractedBinary = findFileRecursive(extractDir, binaryName);
150
+ if (!extractedBinary) {
151
+ throw new Error(`Extracted archive does not contain ${binaryName}`);
152
+ }
144
153
 
145
- fs.copyFileSync(extractedBinary, dest);
146
- if (platform !== "win32") {
147
- fs.chmodSync(dest, 0o755);
154
+ const dest = path.join(binDir, binaryName);
155
+ fs.copyFileSync(extractedBinary, dest);
156
+ if (platform !== "win32") {
157
+ fs.chmodSync(dest, 0o755);
158
+ }
159
+ destinations[binaryName] = dest;
148
160
  }
149
161
 
150
- return { asset, dest };
162
+ return { asset, destinations };
151
163
  } finally {
152
164
  cleanupTempDir(tempRoot);
153
165
  }
@@ -157,7 +169,6 @@ async function installReleaseBinary({
157
169
  repo,
158
170
  version,
159
171
  binDir,
160
- binaryName = binaryNameForPlatform(),
161
172
  platform = os.platform(),
162
173
  arch = os.arch(),
163
174
  }) {
@@ -167,11 +178,10 @@ async function installReleaseBinary({
167
178
 
168
179
  try {
169
180
  await download(url, archivePath);
170
- return installBinaryFromArchive({
181
+ return installBundleFromArchive({
171
182
  archivePath,
172
183
  asset,
173
184
  binDir,
174
- binaryName,
175
185
  platform,
176
186
  });
177
187
  } finally {
@@ -181,8 +191,11 @@ async function installReleaseBinary({
181
191
 
182
192
  module.exports = {
183
193
  binaryNameForPlatform,
194
+ bundleBinaryNamesForPlatform,
195
+ daemonBinaryNameForPlatform,
184
196
  download,
185
- installBinaryFromArchive,
197
+ installBinaryFromArchive: installBundleFromArchive,
198
+ installBundleFromArchive,
186
199
  installReleaseBinary,
187
200
  releaseAssetName,
188
201
  releaseAssetUrl,
@@ -6,7 +6,8 @@ const { execFileSync } = require("child_process");
6
6
 
7
7
  const {
8
8
  binaryNameForPlatform,
9
- installBinaryFromArchive,
9
+ bundleBinaryNamesForPlatform,
10
+ installBundleFromArchive,
10
11
  releaseAssetName,
11
12
  } = require("./release-assets.cjs");
12
13
  const postinstall = require("./postinstall.cjs");
@@ -39,57 +40,82 @@ run("release helper keeps platform binary names stable", () => {
39
40
  assert.equal(binaryNameForPlatform("darwin"), "gwt");
40
41
  });
41
42
 
43
+ run("release helper keeps bundle binary names stable", () => {
44
+ assert.deepEqual(bundleBinaryNamesForPlatform("win32"), ["gwt.exe", "gwtd.exe"]);
45
+ assert.deepEqual(bundleBinaryNamesForPlatform("linux"), ["gwt", "gwtd"]);
46
+ assert.deepEqual(bundleBinaryNamesForPlatform("darwin"), ["gwt", "gwtd"]);
47
+ });
48
+
42
49
  run("installer entrypoints are loadable under package type module", () => {
43
50
  assert.equal(typeof postinstall.main, "function");
44
51
  assert.equal(typeof launcher.main, "function");
45
52
  });
46
53
 
47
- run("portable tarball extraction installs the unix binary", () => {
54
+ run("windows installer definition includes the gwtd companion binary", () => {
55
+ const wix = fs.readFileSync(path.join(__dirname, "..", "wix", "main.wxs"), "utf8");
56
+ assert.match(wix, /gwtd\.exe/);
57
+ });
58
+
59
+ run("release workflow packages gwtd alongside gwt", () => {
60
+ const workflow = fs.readFileSync(
61
+ path.join(__dirname, "..", ".github", "workflows", "release.yml"),
62
+ "utf8"
63
+ );
64
+ assert.match(workflow, /--bin gwt --bin gwtd/);
65
+ assert.match(workflow, /Compress-Archive -Path @\("dist\/gwt\.exe", "dist\/gwtd\.exe"\)/);
66
+ assert.match(workflow, /tar -czf \$\{\{ matrix\.archive_name \}\} gwt gwtd/);
67
+ assert.match(workflow, /Contents\/MacOS\/gwtd/);
68
+ });
69
+
70
+ run("portable tarball extraction installs the unix bundle", () => {
48
71
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "gwt-release-test-"));
49
72
  const sourceDir = path.join(root, "source");
50
73
  const binDir = path.join(root, "bin");
51
74
  const archivePath = path.join(root, "gwt-linux-x86_64.tar.gz");
52
75
  fs.mkdirSync(sourceDir, { recursive: true });
53
76
  fs.writeFileSync(path.join(sourceDir, "gwt"), "unix-binary");
77
+ fs.writeFileSync(path.join(sourceDir, "gwtd"), "unix-daemon");
54
78
 
55
- execFileSync("tar", ["-czf", archivePath, "-C", sourceDir, "gwt"]);
79
+ execFileSync("tar", ["-czf", archivePath, "-C", sourceDir, "gwt", "gwtd"]);
56
80
 
57
- installBinaryFromArchive({
81
+ installBundleFromArchive({
58
82
  archivePath,
59
83
  asset: path.basename(archivePath),
60
84
  binDir,
61
- binaryName: "gwt",
62
85
  platform: "linux",
63
86
  });
64
87
 
65
88
  assert.equal(fs.readFileSync(path.join(binDir, "gwt"), "utf8"), "unix-binary");
89
+ assert.equal(fs.readFileSync(path.join(binDir, "gwtd"), "utf8"), "unix-daemon");
66
90
  });
67
91
 
68
- run("portable zip extraction installs the windows binary", () => {
92
+ run("portable zip extraction installs the windows bundle", () => {
69
93
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "gwt-release-test-"));
70
94
  const sourceDir = path.join(root, "source");
71
95
  const binDir = path.join(root, "bin");
72
96
  const archivePath = path.join(root, "gwt-windows-x86_64.zip");
73
97
  const sourceBinary = path.join(sourceDir, "gwt.exe");
98
+ const sourceDaemon = path.join(sourceDir, "gwtd.exe");
74
99
  fs.mkdirSync(sourceDir, { recursive: true });
75
100
  fs.writeFileSync(sourceBinary, "windows-binary");
101
+ fs.writeFileSync(sourceDaemon, "windows-daemon");
76
102
 
77
103
  execFileSync("powershell.exe", [
78
104
  "-NoProfile",
79
105
  "-NonInteractive",
80
106
  "-Command",
81
- `Compress-Archive -LiteralPath '${sourceBinary.replace(/'/g, "''")}' -DestinationPath '${archivePath.replace(/'/g, "''")}' -Force`,
107
+ `Compress-Archive -LiteralPath @('${sourceBinary.replace(/'/g, "''")}','${sourceDaemon.replace(/'/g, "''")}') -DestinationPath '${archivePath.replace(/'/g, "''")}' -Force`,
82
108
  ]);
83
109
 
84
- installBinaryFromArchive({
110
+ installBundleFromArchive({
85
111
  archivePath,
86
112
  asset: path.basename(archivePath),
87
113
  binDir,
88
- binaryName: "gwt.exe",
89
114
  platform: "win32",
90
115
  });
91
116
 
92
117
  assert.equal(fs.readFileSync(path.join(binDir, "gwt.exe"), "utf8"), "windows-binary");
118
+ assert.equal(fs.readFileSync(path.join(binDir, "gwtd.exe"), "utf8"), "windows-daemon");
93
119
  });
94
120
 
95
121
  if (failed) {
@@ -43,7 +43,9 @@ require_contains "$PACKAGE_JSON" "\"lint:husky\": \"bash scripts/verify-husky-ho
43
43
  require_file "$PRE_PUSH"
44
44
  require_contains "$PRE_PUSH" "cargo clippy --all-targets --all-features -- -D warnings"
45
45
  require_contains "$PRE_PUSH" "cargo fmt --all -- --check"
46
- require_contains "$PRE_PUSH" "bunx --bun markdownlint-cli . --config .markdownlint.json --ignore target --ignore CHANGELOG.md"
46
+ require_contains "$PRE_PUSH" "cargo llvm-cov -p gwt-core -p gwt --all-features --json --summary-only --output-path target/coverage-summary.json"
47
+ require_contains "$PRE_PUSH" "node scripts/check-coverage-threshold.mjs target/coverage-summary.json 90"
48
+ require_contains "$PRE_PUSH" "bunx --bun markdownlint-cli . --config .markdownlint.json --ignore target --ignore CHANGELOG.md --ignore tasks/todo.md"
47
49
  require_contains "$PRE_PUSH" "pnpm lint:skills"
48
50
 
49
51
  require_file "$COMMIT_MSG"
@@ -52,8 +54,6 @@ require_contains "$COMMIT_MSG" 'bunx --package @commitlint/cli commitlint --edit
52
54
  if [ -f "$PRE_COMMIT" ]; then
53
55
  require_contains "$PRE_COMMIT" "pnpm lint:skills"
54
56
  require_contains "$PRE_COMMIT" "bash scripts/run-local-backend-tests-on-commit.sh"
55
- require_contains "$PRE_COMMIT" "bash scripts/run-local-e2e-on-commit.sh"
56
- require_contains "$PRE_COMMIT" "bash scripts/run-local-e2e-coverage-on-commit.sh"
57
57
  require_not_contains "$PRE_COMMIT" "cargo clippy --all-targets --all-features -- -D warnings"
58
58
  require_not_contains "$PRE_COMMIT" "cargo fmt --all -- --check"
59
59
  require_not_contains "$PRE_COMMIT" "markdownlint-cli"
package/wix/main.wxs CHANGED
@@ -26,6 +26,9 @@
26
26
  <Component>
27
27
  <File Id="GwtExecutable" Source="!(bindpath.gwt)gwt.exe" KeyPath="yes" />
28
28
  </Component>
29
+ <Component>
30
+ <File Id="GwtdExecutable" Source="!(bindpath.gwt)gwtd.exe" KeyPath="yes" />
31
+ </Component>
29
32
  </ComponentGroup>
30
33
  </Fragment>
31
34
  </Wix>