wp2txt 2.3.2 → 2.3.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 874d5f0c6463fc612080339cbbcbe560b51aba31a8a5e9478d95719c53744051
4
- data.tar.gz: e8377c0df46cd7d35ca8a85cea4f52e6dc4ca57330004ce5fc6f11913aa1abcf
3
+ metadata.gz: 051b5501ecf2aad35ca972af6527c5afbce88222c17e9c93dc76f806b91a8e9d
4
+ data.tar.gz: 02efabb7102da40c144a3c8204f564a8a40dfa1e77a54813e40bd6740693a264
5
5
  SHA512:
6
- metadata.gz: ce2ca7660379c37a3f36d6d731aa3a9c1798998c10dfb5ff012503f571653b26aef6324e579c1a8af7f0d2ff52cf90fc8c6a50f44f21d40b73d132277cb50a26
7
- data.tar.gz: d8197213c46510742d015dcea77c8ff5c40d48dac7beff8cc409ce1e4ce5f3e138e47d7df9ce675a0614785b21bb266664f8c082c94080c4f3a29c712ffeab07
6
+ metadata.gz: d89ff3e231200c36f647284fee43565021c69c73ac8d9f23ca42b58008d2d0d3ac5598fe63aa3736a45ce933622dd28f649f721635a63e782d638f444602d08b
7
+ data.tar.gz: c70fccdb332688f805e77dbefd291563045e2f53c07e7cc994dbff0a7f8858dded28f18bce4bcb1296e7594f12ee54eea12a4573beee04a8944d4e39b7d35620
data/.dockerignore CHANGED
@@ -15,6 +15,9 @@ scripts
15
15
  .solargraph.yml
16
16
  .rubocop.yml
17
17
  Gemfile.lock
18
+ .private-doc-tokens
19
+ **/.DS_Store
20
+ .ruby-version
18
21
  CLAUDE.md
19
22
  DEVELOPMENT.md
20
23
  DEVELOPMENT_ja.md
@@ -0,0 +1,140 @@
1
+ name: Publish image
2
+
3
+ # Publishes the multi-arch container image to the GitHub Container Registry.
4
+ #
5
+ # Publishing happens here rather than from a maintainer's machine on purpose. A
6
+ # local build sends the working tree as the build context, so anything sitting
7
+ # there untracked rides along; a runner starts from a clean checkout, where such
8
+ # files do not exist. Twice, private files reached published images this way.
9
+ #
10
+ # Fires on a v* tag. Run it by hand from the Actions tab for a dry run: the
11
+ # default leaves `push` off, so it builds and gates without publishing.
12
+
13
+ on:
14
+ push:
15
+ tags: ['v*']
16
+ workflow_dispatch:
17
+ inputs:
18
+ push:
19
+ description: 'Publish to GHCR (off = build and gate only)'
20
+ type: boolean
21
+ default: false
22
+
23
+ env:
24
+ IMAGE: ghcr.io/yohasebe/wp2txt
25
+
26
+ jobs:
27
+ publish:
28
+ name: Build, gate, publish
29
+ runs-on: ubuntu-latest
30
+ permissions:
31
+ contents: read
32
+ packages: write
33
+
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+
37
+ - name: Work out the version and whether to publish
38
+ id: plan
39
+ run: |
40
+ set -euo pipefail
41
+ file_version=$(grep -oE '[0-9]+\.[0-9]+\.[0-9]+' lib/wp2txt/version.rb | head -1)
42
+ if [ -z "$file_version" ]; then
43
+ echo "could not read a version from lib/wp2txt/version.rb" >&2
44
+ exit 1
45
+ fi
46
+
47
+ if [ "${GITHUB_EVENT_NAME}" = "push" ]; then
48
+ tag_version="${GITHUB_REF_NAME#v}"
49
+ if [ "$tag_version" != "$file_version" ]; then
50
+ echo "tag ${GITHUB_REF_NAME} disagrees with lib/wp2txt/version.rb (${file_version})" >&2
51
+ echo "Publishing would tag the image with a version the code does not claim." >&2
52
+ exit 1
53
+ fi
54
+ publish=true
55
+ else
56
+ publish="${{ inputs.push }}"
57
+ fi
58
+
59
+ echo "version=$file_version" >> "$GITHUB_OUTPUT"
60
+ echo "publish=$publish" >> "$GITHUB_OUTPUT"
61
+ echo "Version $file_version; publish=$publish"
62
+
63
+ - uses: ruby/setup-ruby@v1
64
+ with:
65
+ ruby-version: '3.3'
66
+
67
+ - uses: docker/setup-qemu-action@v3
68
+
69
+ - uses: docker/setup-buildx-action@v3
70
+
71
+ # Single arch, loaded into the daemon, so the gate can open it. The
72
+ # multi-arch build below reuses these layers from the builder's cache.
73
+ - name: Build for inspection (linux/amd64)
74
+ uses: docker/build-push-action@v6
75
+ with:
76
+ context: .
77
+ platforms: linux/amd64
78
+ load: true
79
+ push: false
80
+ tags: ${{ env.IMAGE }}:gate-candidate
81
+
82
+ - name: Gate — compare the image against the build context
83
+ run: ruby scripts/verify_image.rb "${IMAGE}:gate-candidate"
84
+
85
+ - name: Log in to GHCR
86
+ if: steps.plan.outputs.publish == 'true'
87
+ uses: docker/login-action@v3
88
+ with:
89
+ registry: ghcr.io
90
+ username: ${{ github.actor }}
91
+ password: ${{ secrets.GITHUB_TOKEN }}
92
+
93
+ - name: Build and push (linux/amd64, linux/arm64)
94
+ id: publish
95
+ if: steps.plan.outputs.publish == 'true'
96
+ uses: docker/build-push-action@v6
97
+ with:
98
+ context: .
99
+ platforms: linux/amd64,linux/arm64
100
+ push: true
101
+ tags: |
102
+ ${{ env.IMAGE }}:${{ steps.plan.outputs.version }}
103
+ ${{ env.IMAGE }}:latest
104
+ labels: |
105
+ org.opencontainers.image.source=https://github.com/yohasebe/wp2txt
106
+ org.opencontainers.image.version=${{ steps.plan.outputs.version }}
107
+
108
+ # Reading it back without credentials is the only proof that a user can
109
+ # pull what was just published.
110
+ - name: Check the published digest is readable anonymously
111
+ if: steps.plan.outputs.publish == 'true'
112
+ run: |
113
+ set -euo pipefail
114
+ digest="${{ steps.publish.outputs.digest }}"
115
+ docker logout ghcr.io
116
+ for attempt in 1 2 3 4 5; do
117
+ if docker manifest inspect "${IMAGE}@${digest}" > /dev/null 2>&1; then
118
+ echo "readable anonymously: ${IMAGE}@${digest}"
119
+ exit 0
120
+ fi
121
+ echo "attempt ${attempt} failed; ghcr.io can lag a few seconds, retrying in 10s"
122
+ sleep 10
123
+ done
124
+ echo "${IMAGE}@${digest} is not readable anonymously" >&2
125
+ exit 1
126
+
127
+ - name: Summary
128
+ if: always()
129
+ run: |
130
+ {
131
+ echo "### wp2txt image"
132
+ echo
133
+ echo "- version: \`${{ steps.plan.outputs.version }}\`"
134
+ echo "- published: \`${{ steps.plan.outputs.publish }}\`"
135
+ if [ -n "${{ steps.publish.outputs.digest }}" ]; then
136
+ echo "- digest: \`${{ steps.publish.outputs.digest }}\`"
137
+ echo "- pull: \`docker pull ${IMAGE}:${{ steps.plan.outputs.version }}\`"
138
+ fi
139
+ echo "- run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
140
+ } >> "$GITHUB_STEP_SUMMARY"
data/CHANGELOG.md CHANGED
@@ -8,6 +8,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  Entries were rewritten in August 2026 to describe what changed for people using
9
9
  wp2txt, rather than how it was implemented. The changes themselves are unaltered.
10
10
 
11
+ ## [2.3.3] - 2026-09-08
12
+
13
+ - **Short searches no longer report a false zero**: in Japanese, Chinese, and Korean indexes a phrase of one or two characters could match nothing and come back as `0 matches`, which reads exactly like a term that is genuinely absent from the dump. Such a search now fails with an explicit error instead. If you recorded a zero result for a short term with an earlier version, re-check it
14
+ - **Characters no longer vanish from extracted text**: a multi-byte character that happened to straddle an internal read boundary was dropped, silently and at unpredictable positions. Any non-ASCII text could be affected. Re-extract if you need the output to be exact
15
+ - **Articles whose title contains a colon are no longer skipped**: `Star Trek: Voyager` and titles like it were mistaken for administrative pages and left out of both extraction and indexes. Rebuild with `--build-index -U` and re-extract to pick them up
16
+ - **`query_sql` no longer loses values when column names repeat**: a query selecting two columns under the same name kept only one of them. Names are now made unique, and the `.meta.json` beside a file result records which output name corresponds to which column of your query
17
+ - **Cell clipping counts bytes, not characters**: a long cell was measured in characters, so it could exceed the limit and still be reported as unclipped — 30,000 Japanese characters are 90,000 bytes. The limit is now 65,536 bytes, cut at a character boundary, and the count of clipped cells is accurate
18
+ - **A result of exactly the requested size is no longer marked truncated**: `truncated` came back true when a result happened to fill the limit exactly, with nothing left behind
19
+ - **Output files stay inside the output directory**: a symbolic link within it could redirect a write outside, the `.meta.json` sidecar was not checked at all, and an existing file could slip past `overwrite: false`. Output and sidecar are now reserved together and written to a temporary file first, so a failed or cancelled run leaves your previous output untouched instead of a half-written file in its place
20
+ - **Index builds no longer grow in memory with the size of the dump**: every batch of extracted text was kept until the build finished
21
+ - **A background job that fails to start is now reported as failed**: it stayed `running` indefinitely and blocked every later job until the server was restarted
22
+ - **The gem no longer ships maintainer scripts or images**. If you were running `scripts/fetch_*.rb` from an installed gem, take them from the repository instead
23
+ - **The Docker Hub repository has been removed**: images are published to GitHub Container Registry only. If you still pull from Docker Hub, switch with `docker pull ghcr.io/yohasebe/wp2txt`
24
+
11
25
  ## [2.3.2] - 2026-08-13
12
26
 
13
27
  - **Documentation release — no code changes.** The changelog and guides bundled with the gem and the container image are rewritten to say what each change means for someone using wp2txt, rather than how it was implemented. The guide formerly at `docs/RESEARCH.md` is now [docs/INDEXES.md](docs/INDEXES.md) ("Offline Indexes, Queries, and the MCP Server")
data/DEVELOPMENT.md CHANGED
@@ -400,13 +400,21 @@ docs/INDEXES.md is checked against the actual server surface by spec/docs_sync_s
400
400
 
401
401
  ## Docker
402
402
 
403
- Build and push Docker images:
403
+ Images are published by GitHub Actions when a `v*` tag is pushed
404
+ (`.github/workflows/publish-image.yml`), not from a maintainer's machine: a
405
+ local build sends the working tree as its context, so untracked files ride
406
+ along, while a runner starts from a clean checkout.
404
407
 
405
408
  ```bash
406
- rake check_image # Builds the image locally and verifies it carries no private files
407
- rake push # Verifies, then builds multi-arch and pushes to GHCR
409
+ rake check_image # Build locally and run the same gate the workflow runs
408
410
  ```
409
411
 
412
+ The gate (`scripts/verify_image.rb`) lists what the image holds under `/wp2txt`
413
+ and compares it against what Docker sends as the build context, in both
414
+ directions. Anything unaccounted for fails, so a file the build starts
415
+ producing must be added to `BUILD_ARTIFACTS` deliberately. To rehearse a
416
+ publish, run the workflow from the Actions tab with **push** left off.
417
+
410
418
  ## Release Process
411
419
 
412
420
  1. Update version in `lib/wp2txt/version.rb`
@@ -414,7 +422,7 @@ rake push # Verifies, then builds multi-arch and pushes to GHCR
414
422
  3. Run full test suite: `bundle exec rspec`
415
423
  4. Build gem: `gem build wp2txt.gemspec`
416
424
  5. Push to RubyGems: `gem push wp2txt-*.gem`
417
- 6. Push Docker image: `rake push`
425
+ 6. Push the `v*` tag — GitHub Actions builds, gates, and publishes the image
418
426
  7. Create GitHub release
419
427
 
420
428
  ## Useful Links
data/DEVELOPMENT_ja.md CHANGED
@@ -402,8 +402,7 @@ docs/INDEXES.md の MCP ツール表は spec/docs_sync_spec.rb が実際のサ
402
402
  Dockerイメージのビルドとプッシュ:
403
403
 
404
404
  ```bash
405
- rake check_image # ローカルでイメージをビルドし、私的ファイルの混入がないか検証
406
- rake push # 検証したうえでマルチアーキテクチャでビルドしGHCRにプッシュ
405
+ rake check_image # ローカルでビルドし、CI と同じゲートを走らせる
407
406
  ```
408
407
 
409
408
  ## リリースプロセス
@@ -413,7 +412,7 @@ rake push # 検証したうえでマルチアーキテクチャでビル
413
412
  3. フルテストスイートを実行: `bundle exec rspec`
414
413
  4. gemをビルド: `gem build wp2txt.gemspec`
415
414
  5. RubyGemsにプッシュ: `gem push wp2txt-*.gem`
416
- 6. Dockerイメージをプッシュ: `rake push`
415
+ 6. `v*` タグをプッシュ — GitHub Actions がビルド・検証・公開を行う
417
416
  7. GitHubリリースを作成
418
417
 
419
418
  ## 便利なリンク
data/README.md CHANGED
@@ -84,7 +84,7 @@ docker run -it -v /path/to/localdata:/data ghcr.io/yohasebe/wp2txt
84
84
 
85
85
  The `wp2txt` command is available inside the container. Use `/data` for input/output files.
86
86
 
87
- Images are published to GitHub Container Registry (`ghcr.io/yohasebe/wp2txt`). The former Docker Hub repository (`yohasebe/wp2txt`) is no longer updated as of 2.3.1.
87
+ Images are published to GitHub Container Registry (`ghcr.io/yohasebe/wp2txt`). The former Docker Hub repository has been removed pull from GHCR.
88
88
 
89
89
  **MCP server (no Ruby required on the host):**
90
90
 
data/README_ja.md CHANGED
@@ -80,7 +80,7 @@ docker run -it -v /path/to/localdata:/data ghcr.io/yohasebe/wp2txt
80
80
 
81
81
  `wp2txt`コマンドはコンテナ内で使用可能です。入出力には`/data`ディレクトリを使用してください。
82
82
 
83
- イメージはGitHub Container Registry(`ghcr.io/yohasebe/wp2txt`)で公開されています。旧 Docker Hub リポジトリ(`yohasebe/wp2txt`)は 2.3.1 以降更新されません。
83
+ イメージはGitHub Container Registry(`ghcr.io/yohasebe/wp2txt`)で公開されています。旧 Docker Hub リポジトリは削除済みです GHCR から pull してください。
84
84
 
85
85
  ## 基本的な使い方
86
86
 
data/Rakefile CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
+ require "open3"
4
5
  require "rspec/core"
5
6
  require "rspec/core/rake_task"
6
7
  require_relative "./lib/wp2txt/version"
@@ -28,6 +29,10 @@ end
28
29
 
29
30
  Rake::Task["build"].enhance([:normalize_permissions])
30
31
 
32
+ # Pre-release gate: verify the built gem's payload against spec.files and scan
33
+ # it for names, content, and modes that must never ship (code-security protocol).
34
+ Rake::Task["build"].enhance { sh "ruby", "scripts/verify_gem.rb" }
35
+
31
36
  # =============================================================================
32
37
  # Docker
33
38
  # =============================================================================
@@ -35,13 +40,14 @@ Rake::Task["build"].enhance([:normalize_permissions])
35
40
  # Paths that must never reach a published image. The image is built from the
36
41
  # working tree, so anything ignored locally (private notes, scratch files)
37
42
  # would otherwise ride along.
38
- IMAGE_FORBIDDEN_PATHS = %w[/wp2txt/research-notes /wp2txt/tmp /wp2txt/.git /wp2txt/CLAUDE.md /wp2txt/.claude].freeze
43
+ IMAGE_FORBIDDEN_PATHS = %w[/wp2txt/research-notes /wp2txt/tmp /wp2txt/.git /wp2txt/CLAUDE.md /wp2txt/.claude /wp2txt/.private-doc-tokens].freeze
39
44
 
40
45
  desc "Verify a built image contains no private material (run before pushing)"
41
46
  task :verify_image, [:tag] do |_t, args|
42
47
  tag = args[:tag] || "wp2txt-verify:local"
43
48
  checks = IMAGE_FORBIDDEN_PATHS.map { |p| "test -e #{p} && echo LEAK:#{p}" }.join("; ")
44
- out = `docker run --rm #{tag} sh -c '#{checks}; true' 2>&1`
49
+ out, status = Open3.capture2e("docker", "run", "--rm", tag, "sh", "-c", "#{checks}; true")
50
+ abort "Image verification failed for #{tag}: #{out}" unless status.success?
45
51
  leaks = out.lines.grep(/^LEAK:/).map(&:strip)
46
52
  abort "Image #{tag} contains private paths:\n #{leaks.join("\n ")}" unless leaks.empty?
47
53
 
@@ -54,18 +60,18 @@ task :check_image do
54
60
  Rake::Task[:verify_image].invoke
55
61
  end
56
62
 
57
- desc "Build and push Docker images to GHCR (verifies a local build first)"
58
- task push: :check_image do
59
- # Docker Hub was retired after 2.3.0; GHCR (ghcr.io/yohasebe/wp2txt) is the
60
- # only registry. Requires `docker buildx use multiarch` and a ghcr.io login.
61
- sh <<-SCRIPT.strip_heredoc, { verbose: false }
62
- /bin/bash -xeu <<'BASH'
63
- # docker buildx create --name multiarch
64
- # docker buildx use multiarch
65
- # docker buildx inspect --bootstrap
66
- docker buildx build --platform linux/amd64,linux/arm64 \
67
- -t ghcr.io/yohasebe/wp2txt:#{Wp2txt::VERSION} -t ghcr.io/yohasebe/wp2txt:latest \
68
- . --push
69
- BASH
70
- SCRIPT
63
+ desc "Explain how images are published (they are built and pushed by CI)"
64
+ task :push do
65
+ abort <<~MESSAGE
66
+ Images are published by GitHub Actions, not from here.
67
+
68
+ A local build sends this working tree as the build context, so untracked
69
+ files ride along; a runner starts from a clean checkout, where they do not
70
+ exist. Push a v* tag and .github/workflows/publish-image.yml takes over:
71
+
72
+ rake release # tags and pushes (also publishes the gem)
73
+
74
+ To rehearse without publishing, run the workflow from the Actions tab with
75
+ "push" left off. To check a local build, run `rake check_image`.
76
+ MESSAGE
71
77
  end
data/bin/wp2txt CHANGED
@@ -217,7 +217,8 @@ class WpApp
217
217
  next unless title_match
218
218
 
219
219
  title = title_match[1]
220
- next if title.nil? || title.empty? || title.include?(":")
220
+ next if title.nil? || title.empty?
221
+ next unless Wp2txt.namespace_id(page_xml[%r{<ns>(-?\d+)</ns>}, 1]).zero?
221
222
 
222
223
  # Extract text content
223
224
  text_match = TEXT_REGEX.match(page_xml)
@@ -609,7 +610,8 @@ class WpApp
609
610
  next unless title_match
610
611
 
611
612
  title = title_match[1]
612
- next if title.nil? || title.empty? || title.include?(":")
613
+ next if title.nil? || title.empty?
614
+ next unless Wp2txt.namespace_id(page_xml[%r{<ns>(-?\d+)</ns>}, 1]).zero?
613
615
 
614
616
  text_match = TEXT_REGEX.match(page_xml)
615
617
  next unless text_match
data/bin/wp2txt-mcp CHANGED
@@ -73,7 +73,7 @@ def respond(&block)
73
73
  result = block.call
74
74
  MCP::Tool::Response.new([{ type: "text", text: JSON.generate(result) }])
75
75
  rescue ArgumentError => e
76
- MCP::Tool::Response.new([{ type: "text", text: JSON.generate({ error: e.message }) }], error: true)
76
+ MCP::Tool::Response.new([{ type: "text", text: JSON.generate({ error: e.message, code: (e.code if e.respond_to?(:code)) }.compact) }], error: true)
77
77
  rescue StandardError => e
78
78
  MCP::Tool::Response.new([{ type: "text", text: JSON.generate({ error: "#{e.class}: #{e.message}" }) }], error: true)
79
79
  end
@@ -312,7 +312,8 @@ def extract_params(output_path:, content:, titles: nil, sections: nil, alias_set
312
312
  content: content, titles: titles,
313
313
  sections: sections, alias_set: alias_set, category: category, depth: depth,
314
314
  categories: categories, category_match: category_match,
315
- title_match: title_match, limit: limit, chunk_size: chunk_size, chunk_overlap: chunk_overlap }
315
+ title_match: title_match, limit: limit, chunk_size: chunk_size, chunk_overlap: chunk_overlap,
316
+ overwrite: overwrite }
316
317
  end
317
318
 
318
319
  server.define_tool(
@@ -341,7 +342,7 @@ end
341
342
 
342
343
  server.define_tool(
343
344
  name: "cancel_job",
344
- description: "Request cancellation of a running job (takes effect at the next batch boundary; the partial output file remains on disk).",
345
+ description: "Request cancellation of a running job (takes effect at the next batch boundary; staged output is removed).",
345
346
  input_schema: { properties: { job_id: { type: "string" } }, required: ["job_id"] }
346
347
  ) do |job_id:, server_context:|
347
348
  respond { job_manager.cancel(job_id) || { error: "unknown job: #{job_id}" } }
data/docs/INDEXES.md CHANGED
@@ -130,6 +130,10 @@ $ claude mcp add wp2txt -- docker run -i --rm -v wp2txt:/root/.wp2txt ghcr.io/yo
130
130
  seconds, saved alias sets are re-checked before being stored, and files can only be
131
131
  written under the server's output directory — worth knowing if you plan to let an
132
132
  assistant work unattended.
133
+ Output confinement assumes a dedicated, trusted output directory: symlink paths are
134
+ rejected, both output and sidecar are exclusively reserved unless overwrite is requested,
135
+ and unique temporary files are renamed on success; concurrent replacement of parent
136
+ directories is outside this guarantee.
133
137
 
134
138
  ## 5. Cross-language SQL
135
139
 
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wp2txt
4
+ # Missing namespace elements use the metadata scanner's historical ns=0 default.
5
+ def self.namespace_id(value)
6
+ (value || "0").to_i
7
+ end
8
+
4
9
  # =========================================================================
5
10
  # Custom Exception Classes
6
11
  # =========================================================================