ctx_cli 0.2.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2e8622dd69aeb8ad48fb4e9b5d0212345698f81b78f042d4dfa761bdf0c3890
4
- data.tar.gz: b579bf0a5ad0a3d795249f4e8d0048366120f5a8f857f95a29901c78266b28f2
3
+ metadata.gz: 993414ac12bd17c37f136bb85d871efc2f217c7ca81fb7c9c57faca557030686
4
+ data.tar.gz: d1cfd1282ef0e9ddf5cd23243bf09076d0ffc8fed4c00186f5471e5e96bbcb94
5
5
  SHA512:
6
- metadata.gz: 2e18324d1c852f300873ea0bf02fadd159fd70bf4637f716590e1e0435d92d5dc1a1580e5eed4f552cc607cf2aeef5549df544a8f07f1340faff5cc87724ecc3
7
- data.tar.gz: b61f9d0812d4bde92d8b5bf86d804bc9d9f2b8991b7f3b7c308909c74696c4ecfec333dd1ef9b14ae9da57c043cf91a35b9ee74d62eaeea6bd17398e015222c6
6
+ metadata.gz: 6a72826b05f4e540be4999b61acbf9e89517c41949ccefd475e2e67ea039e39a49754f4ddcf179de83f164954032c0d250463db0b0ac6925d51e11665d98b6d2
7
+ data.tar.gz: aa88dbda8a415b0f3db1a52a9283a6711951e35c5003814f75724e1ace72ae35472d7f09beda808597c04f746d6dabe7fbd6946f4a55f241cd104c05e831c5a5
data/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.0 (2026-10-24)
6
+
7
+ - Use JSON Pointer keys in configuration field provenance, separating literal dots, slashes, tildes, empty keys and nested mappings.
8
+ - Remove only actual descendant sources when a mapping is replaced; preserve array replacement and empty-mapping merge behavior.
9
+ - Breaking change: sources.fields and host --show-config output no longer expose dotted-path aliases; migrate field lookups to JSON Pointer.
10
+
11
+ ## 0.2.1 (2026-10-23)
12
+
13
+ - Use English command help, prompts and collection/review diagnostics.
14
+ - Allow compatible dotenv versions without an upper version bound.
15
+
5
16
  ## 0.2.0 (2026-10-23)
6
17
 
7
18
  - Rename Initialization to Initializer and Application to Router; remove the old constants and require paths in the 0.2.0 candidate.
data/README.md CHANGED
@@ -53,6 +53,8 @@ files.environment # .env 加原进程环境;不修改 ENV
53
53
  files.sources # 路径与字段来源,不包含环境值
54
54
  ```
55
55
 
56
+ `sources["fields"]` 使用 JSON Pointer 作为字段键,例如 `/parallel`、`/ssh/known_hosts`。字面键 `a.b` 对应 `/a.b`,嵌套 `a → b` 对应 `/a/b`;键内 `/` 编为 `~1`、`~` 编为 `~0`。数组作为整体字段记录来源。宿主的 `path_fields` 声明语法不变,来源索引不保留点分键别名。
57
+
56
58
  默认依次搜索 `/etc/<namespace>`、`/usr/local/etc/<namespace>`、`$XDG_CONFIG_HOME/<namespace>`(否则 `~/.config/<namespace>`)、入口目录和启动目录。每层读取 `<namespace>.yml` 与 `<name>.yml`,显式文件最后。共享文件按 `common` 和工具名称分区;name 与 namespace 相同时使用普通独立文件。`directories:` 可以完整替换搜索范围。
57
59
 
58
60
  宿主把各层公共环境值转换成领域字段后,通过 `Configuration.merge(defaults, dotenv_values, yaml, process_values, cli)` 实现 CLI > 进程环境 > YAML > .env > 默认值。数组整层替换,false/nil 保留。相对路径按来源目录解释;支持 `path_literals:`(如 `system`)和 URI。`load` 重复调用不重新读文件。
@@ -73,7 +75,7 @@ Options 协作者沿用现有入口协议:`informational?`、`validate_interac
73
75
  bundle install
74
76
  bundle exec rake
75
77
  bundle exec rake build
76
- ruby script/verify_package.rb pkg/ctx_cli-0.2.0.gem
78
+ ruby script/verify_package.rb pkg/ctx_cli-0.3.0.gem
77
79
  ```
78
80
 
79
81
  默认任务包括 RSpec、RuboCop 和离线示例。安装验证在独立 GEM_HOME/GEM_PATH 下运行普通与最小 Bundler 应用,检查实际加载来源。CI 参照 ctx_parser 的固定 action 提交和候选制品校验;发布需另行明确授权。
data/docs/migration.md CHANGED
@@ -17,4 +17,10 @@ ctx_cli 初始版本为 0.1.0。首次发布依赖顺序先 ctx_cli,再 net-co
17
17
  - `CtxCli::Initialization` / `ctx_cli/initialization` 改为 `CtxCli::Initializer` / `ctx_cli/initializer`,仅负责模板安全发布。
18
18
  - `CtxCli::Application` / `ctx_cli/application` 改为 `CtxCli::Router` / `ctx_cli/router`,仅负责全局信息和子命令分发。
19
19
 
20
- 旧常量和 require 路径不保留别名。宿主需同步修改引用,并将 ctx_cli 依赖更新为 `~> 0.2.0`。`create`、`existing?`、`run` 的结果约定不变;初始化参数询问和厂商语义不迁入这两个对象。0.2.0 当前为本地候选,尚未发布。
20
+ 旧常量和 require 路径不保留别名。宿主需同步修改引用,并在应用锁文件中选择包含新接口的 ctx_cli 版本。`create`、`existing?`、`run` 的结果约定不变;初始化参数询问和厂商语义不迁入这两个对象。这些名称从 0.2.0 起使用。
21
+
22
+ ## 0.3.0 配置来源字段
23
+
24
+ `Configuration#sources["fields"]` 的键统一使用 [JSON Pointer](https://www.rfc-editor.org/rfc/rfc6901):`parallel` 改为 `/parallel`,`ssh.port` 的嵌套路径改为 `/ssh/port`。字面键 `ssh.port` 则为 `/ssh.port`,两者拥有独立来源。空键为 `/`;每段中的 `~` 编为 `~0`,`/` 编为 `~1`,不作 Unicode 归一化。
25
+
26
+ 来源值仍是 YAML 文件路径;数组整体替换,来源记在数组字段上。空映射合并不覆盖既有非空映射的来源。宿主透传的 `--show-config` 同步采用此结构;外部来源索引调用方须迁移键查找,不提供点分键回退或格式开关。`path_fields` 仍是宿主声明哪些字段要解析为文件路径的独立语法。
@@ -0,0 +1,46 @@
1
+ # Configuration Source Paths Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Give every merged configuration field an unambiguous source identity.
6
+
7
+ **Architecture:** Configuration emits JSON Pointer keys in sources.fields. Existing hosts forward the source map unchanged; their tests and documentation migrate together. No new abstraction or alternate format.
8
+
9
+ **Tech Stack:** Ruby, YAML, RSpec/Minitest, Bundler.
10
+
11
+ **Spec:** `docs/superpowers/specs/2026-10-24-configuration-source-paths.md`
12
+
13
+ ## Global Constraints
14
+
15
+ - Ruby >= 3.4; no dependency or version-policy change.
16
+ - lib method comments Chinese; business literals English.
17
+ - Preserve WIP, no commit/push/release.
18
+ - Preserve merge semantics, source-relative paths and domain ownership.
19
+ - JSON Pointer applies to sources.fields only; no dot-path aliases.
20
+
21
+ ## Review Focus
22
+
23
+ - Literal dots/slashes cannot collide with nesting.
24
+ - Tilde escape order preserves literal ~1 and ~0.
25
+ - Empty keys and Unicode keys retain exact identity without normalization.
26
+ - Replacing a parent removes only its actual descendant sources.
27
+ - Arrays and empty mappings keep their existing merge/source semantics.
28
+
29
+ ### Task 1: Replace ambiguous source keys and migrate consumers
30
+
31
+ **Files:**
32
+ - Modify ctx_cli: `lib/ctx_cli/configuration.rb`, `spec/configuration_spec.rb`, `README.md`, `docs/migration.md`, `CHANGELOG.md`.
33
+ - Create ctx_cli: this plan and its spec.
34
+ - Modify VXLAN: `spec/cli/configuration_sources_spec.rb`.
35
+ - Modify Wireless: `spec/cli/options_spec.rb`.
36
+ - Modify net-connector: `test/config_test.rb`, `test/cli_setup_test.rb`.
37
+
38
+ **Interfaces:**
39
+ - Consumes: validated string-keyed YAML mappings and previous merged mapping.
40
+ - Produces: `Configuration#sources["fields"]` mapping from JSON Pointer to file path, deeply frozen; examples `/parallel`, `/ssh/port`, `/a.b`, `/a~1b`, `/m~0n`, `/`.
41
+
42
+ - [x] **Step 1:** Add real layered-YAML regressions for colliding literal and nested keys, parent replacement, escaped tokens, empty/Unicode keys, arrays and nil/false. Migrate existing source expectations to pointer keys.
43
+ - [x] **Step 2:** Run `bundle exec rspec spec/configuration_spec.rb`; expect RED from dot-path collisions or missing pointer keys.
44
+ - [x] **Step 3:** Change `record_fields` prefix construction to escaped slash tokens and descendant removal to slash-separated prefixes. Keep all merge and ownership logic. Update consumer source-key assertions and docs; no compatibility branch.
45
+ - [x] **Step 4:** Run ctx_cli default rake, consumer targeted tests and complete gates in isolated worktrees. Run the existing three-size benchmark before/after; verify same configuration/source counts. Expect green gates and no return to quadratic leaf scans.
46
+ - [x] **Step 5:** One independent final review. Guard-copy only listed files into actual checkouts, validate ordinary consumer gates using exact installed CLI candidate, and run plain/Bundler installed source/preview probes. Compare all package metadata/source bytes. Keep uncommitted work and evidence; mark all steps only with passing receipts.
@@ -0,0 +1,27 @@
1
+ # Configuration 来源身份
2
+
3
+ ## 问题与选择
4
+
5
+ Configuration 合并结果允许任意字符串键,但来源索引把层级拼为点分字符串。字面键 `a.b` 和嵌套 `a → b` 共享索引;用标量替换映射 `a` 时,前缀删除还会误删字面键的来源。这使 `--show-config` 的来源与实际配置不一致。
6
+
7
+ 使用 [RFC 6901](https://www.rfc-editor.org/rfc/rfc6901) JSON Pointer 表达 `sources["fields"]` 的键。每层以 `/` 分隔;token 内 `~` 编为 `~0`,`/` 编为 `~1`。字面点不转义,空键使用 `/`。只生成指针,不增加求值器或依赖。来源值仍为文件路径,不含配置值。
8
+
9
+ 拒绝含点的合法键会人为限制宿主数据;自定义点转义会新增不必要的语法。采用标准指针统一迁移,不保留点分别名或旧格式选择。
10
+
11
+ ## 不变量与职责
12
+
13
+ - 合并、来源相对路径、数组替换、false/nil/缺失、空映射合并语义保持不变。
14
+ - 来源索引的叶子/祖先替换必须与实际合并一致;兄弟字面键不能被前缀删除。
15
+ - 数组是整体替换值,在数组字段指针上记录来源,不逐元素建表;空映射作为值时也记录其字段。
16
+ - `path_fields` 是宿主声明路径字段的既有语法,和来源输出不同,不在此阶段改变。
17
+ - 库保持 Ruby >= 3.4,不依赖 Runner/Parser/connector;lib 方法说明中文、业务字面量英文。
18
+ - VXLAN/Wireless/connector 仍透传来源结构;其领域校验、命令、状态与资源所有权不迁移。
19
+ - 保留所有既有 WIP 和当前依赖政策,不提交、推送或发布。
20
+
21
+ ## 范围
22
+
23
+ 11 个文件:ctx_cli 的 configuration.rb、configuration_spec.rb、README、migration、Changelog、本规格及计划;VXLAN/Wireless 各一个参数来源测试;connector 的 config_test 和 cli_setup_test。没有新增配置项或生产适配器。
24
+
25
+ ## 验收
26
+
27
+ 回归从真实 YAML 层证明字面点与层级不同、slash/tilde/空键/Unicode 不碰撞、替换映射只删除真正后代、数组整体替换、冻结及不含秘密值。先观察旧实现失败,再修改来源键构造与后代前缀。迁移消费端断言后运行四模块 gates、实际入口和精确候选普通/Bundler 安装。使用既有 500/1000/3000 字段基准检查规模趋势,不为共享主机设硬性能阈值。一次独立审查,无双格式接口。
@@ -33,13 +33,13 @@ module CtxCli
33
33
  return 0 if @options.missing?(input: @input, error: @error)
34
34
  @options.load
35
35
  @progress = @progress_factory.call(@options)
36
- @progress.notice("配置:#{@options.config}")
36
+ @progress.notice("Configuration: #{@options.config}")
37
37
  return output(@options.public_values.merge("sources" => @options.sources)) if @options.flags[:show_config]
38
38
  @devices, @inventory_total = @adapter.inventory(@options, progress: @progress)
39
39
  devices = @devices
40
40
  return output(devices) if @options.flags[:inventory_only]
41
41
  if devices.empty?
42
- @progress.notice("没有匹配设备,未建立连接")
42
+ @progress.notice("No devices matched; no connections opened")
43
43
  @output.json({ status: :no_devices, total: 0 }, pretty: false) if @options.flags[:json]
44
44
  return 1
45
45
  end
@@ -47,7 +47,7 @@ module CtxCli
47
47
  @store = @adapter.report(@options)
48
48
  @batch = @batch_class.new(report: @store, progress: @progress, requested: devices)
49
49
  outcome = @batch.run(present: ->(summary) { @output.json(summary) if @options.flags[:json] }) do
50
- @progress.plan(devices: devices, total: @inventory_total, parallel: @options.values.fetch("parallel"), label: "#{@label} 只读采集")
50
+ @progress.plan(devices: devices, total: @inventory_total, parallel: @options.values.fetch("parallel"), label: "#{@label} read-only collection")
51
51
  @progress.with_updates do
52
52
  @adapter.execute(devices, @options) do |device|
53
53
  @batch.collect(device) { @adapter.collect(device, @options, context: context, progress: @progress) }
@@ -57,19 +57,19 @@ module CtxCli
57
57
  end
58
58
  completion(outcome)
59
59
  rescue Interrupt
60
- diagnostic "采集已取消,已保存的设备报告保留;未保存结果见报告状态"
60
+ diagnostic "Collection cancelled; saved device reports retained; check report status for unsaved results"
61
61
  130
62
62
  rescue SystemCallError, IOError => error
63
- diagnostic "#{@label} 参数或报告错误:#{report_diagnostic(error)}"
63
+ diagnostic "#{@label} argument or report error: #{report_diagnostic(error)}"
64
64
  2
65
65
  rescue Error, Configuration::Error => error
66
- diagnostic "#{@label} 参数、依赖或报告错误:#{error.message};使用 --help 查看参数"
66
+ diagnostic "#{@label} argument, dependency or report error: #{error.message}; use --help for options"
67
67
  2
68
68
  rescue OptionParser::ParseError, ArgumentError, LoadError => error
69
- diagnostic "#{@label} 参数、依赖或报告错误:#{error.class.name};使用 --help 查看参数"
69
+ diagnostic "#{@label} argument, dependency or report error: #{error.class.name}; use --help for options"
70
70
  2
71
71
  rescue StandardError => error
72
- diagnostic "#{@label} 清单或执行失败:#{@failure_code.call(error)}(#{error.class.name})"
72
+ diagnostic "#{@label} inventory or execution failed: #{@failure_code.call(error)} (#{error.class.name})"
73
73
  2
74
74
  end
75
75
 
@@ -82,18 +82,18 @@ module CtxCli
82
82
  # Runner 决定批次结论;CLI 仅呈现安全诊断并映射退出码。
83
83
  def completion(outcome)
84
84
  if outcome.finalization_error
85
- diagnostic "#{@label} 摘要报告错误:#{outcome.finalization_error}"
85
+ diagnostic "#{@label} summary report error: #{outcome.finalization_error}"
86
86
  end
87
87
  if outcome.recovery_failed
88
- diagnostic "摘要恢复未确认;请依据退出码及报告目录检查,不能将旧摘要视为本次成功。"
88
+ diagnostic "Summary recovery is unconfirmed; check the exit code and report directory. An earlier summary does not confirm this run succeeded."
89
89
  end
90
90
  if outcome.status == :interrupted
91
- diagnostic "采集已取消,已保存的设备报告保留;未保存结果见报告状态"
91
+ diagnostic "Collection cancelled; saved device reports retained; check report status for unsaved results"
92
92
  elsif (failure = outcome.failure)
93
93
  if failure[:kind] == :report
94
- diagnostic "#{@label} 参数或报告错误:#{failure[:detail]}"
94
+ diagnostic "#{@label} argument or report error: #{failure[:detail]}"
95
95
  else
96
- diagnostic "#{@label} 清单或执行失败:#{failure[:error_type]};已采集结果见批次报告"
96
+ diagnostic "#{@label} inventory or execution failed: #{failure[:error_type]}; see the batch report for collected results"
97
97
  end
98
98
  end
99
99
  { complete: 0, incomplete: 1, failed: 2, interrupted: 130 }.fetch(outcome.status)
@@ -115,7 +115,7 @@ module CtxCli
115
115
  @output.json(value)
116
116
  else
117
117
  value.each { |device| @out.puts "#{device.fetch('host')}\t#{device.fetch('name')}\t#{device['model']}" }
118
- @out.puts "设备:#{value.size} 台;未建立设备连接"
118
+ @out.puts "Devices: #{value.size}; no device connections opened"
119
119
  end
120
120
  value.is_a?(Array) && value.empty? ? 1 : 0
121
121
  end
@@ -18,7 +18,7 @@ module CtxCli
18
18
  @parser.banner = @banner
19
19
  @configure&.call(@parser, @values)
20
20
  rest = @parser.parse(argv.dup)
21
- raise ArgumentError, "不接受位置参数" unless rest.empty?
21
+ raise ArgumentError, "Positional arguments are not accepted" unless rest.empty?
22
22
  @values
23
23
  end
24
24
 
@@ -341,9 +341,10 @@ module CtxCli
341
341
  self.class.merge(previous, resolved)
342
342
  end
343
343
 
344
- def record_fields(document, source, fields, prefix = nil, previous: {})
344
+ # 用 JSON Pointer 区分字面键与层级,只移除被替换映射的真正后代来源。
345
+ def record_fields(document, source, fields, prefix = "", previous: {})
345
346
  document.each do |key, value|
346
- name = [ prefix, key ].compact.join(".")
347
+ name = "#{prefix}/#{key.gsub("~", "~0").gsub("/", "~1")}"
347
348
  before = previous.is_a?(Hash) ? previous[key] : nil
348
349
  next if value.is_a?(Hash) && value.empty? && before.is_a?(Hash) && !before.empty?
349
350
 
@@ -353,7 +354,7 @@ module CtxCli
353
354
  else
354
355
  # 只有旧映射被整体替换才需要删除后代;新增/覆盖叶子直接赋值。
355
356
  if before.is_a?(Hash) && !before.empty?
356
- descendant_prefix = "#{name}."
357
+ descendant_prefix = "#{name}/"
357
358
  fields.delete_if { |field, _| field.start_with?(descendant_prefix) }
358
359
  end
359
360
  fields[name] = source
@@ -4,34 +4,34 @@ module CtxCli
4
4
  # 普通参数分组只登记语法;不加载配置、不执行操作、不拥有领域校验。
5
5
  module Options
6
6
  def self.initialization(parser, values)
7
- parser.on("--init", "创建缺失配置和环境模板后退出;已有文件不覆盖") { values[:init] = true }
8
- parser.on("--interactive", "缺少配置时询问是否创建;要求真实终端") { values[:interactive] = true }
9
- parser.on("--non-interactive", "禁用问答;创建文件须同时指定 --init") { values[:interactive] = false }
7
+ parser.on("--init", "Create missing configuration and environment templates, then exit; preserve existing files") { values[:init] = true }
8
+ parser.on("--interactive", "Prompt to create missing configuration; requires an interactive terminal") { values[:interactive] = true }
9
+ parser.on("--non-interactive", "Disable prompts; specify --init to create files") { values[:interactive] = false }
10
10
  end
11
11
 
12
12
  def self.output(parser, values, quiet: :quiet)
13
- parser.on("--json", "stdout 输出 JSON;默认显示可读摘要") { values[:json] = true }
14
- parser.on("--quiet", "--no-progress", "关闭 stderr 进度,可配合 --json") { values[quiet] = true }
15
- parser.on("--verbose", "显示逐台结果及安全命令事件") { values[:verbose] = true }
13
+ parser.on("--json", "Write JSON to stdout; defaults to a readable summary") { values[:json] = true }
14
+ parser.on("--quiet", "--no-progress", "Disable progress on stderr; can be combined with --json") { values[quiet] = true }
15
+ parser.on("--verbose", "Show per-device results and safe command events") { values[:verbose] = true }
16
16
  end
17
17
 
18
18
  def self.information(parser, values)
19
- parser.on("-h", "--help", "查看选项帮助") { values[:help] = true }
20
- parser.on("--help-all", "查看全部选项、路径与环境约定") { values[:help_all] = true }
21
- parser.on("--examples", "查看常用命令范例,不执行任务") { values[:examples] = true }
19
+ parser.on("-h", "--help", "Show option help") { values[:help] = true }
20
+ parser.on("--help-all", "Show all options, paths and environment conventions") { values[:help_all] = true }
21
+ parser.on("--examples", "Show common command examples without executing tasks") { values[:examples] = true }
22
22
  end
23
23
 
24
24
  # 值转换留在宿主边界,允许不同入口沿用原有存储格式。
25
- def self.configuration(parser, parallel_help: "设备并发数", &store)
26
- parser.on("--config FILE", "显式 YAML 最后叠加于自动发现的配置") { |value| store.call(:config, value) }
27
- parser.on("--env-file FILE", "显式环境文件最后叠加;进程环境优先") { |value| store.call(:env_file, value) }
25
+ def self.configuration(parser, parallel_help: "Device concurrency", &store)
26
+ parser.on("--config FILE", "Overlay explicit YAML after automatically discovered configuration") { |value| store.call(:config, value) }
27
+ parser.on("--env-file FILE", "Load explicit environment files last; process environment takes precedence") { |value| store.call(:env_file, value) }
28
28
  parser.on("-p", "--parallel N", Integer, parallel_help) { |value| store.call(:parallel, value) }
29
- parser.on("--directory DIR", "按设备保存报告的目录") { |value| store.call(:directory, value) }
29
+ parser.on("--directory DIR", "Directory for per-device reports") { |value| store.call(:directory, value) }
30
30
  end
31
31
 
32
32
  def self.list(value)
33
33
  values = value.split(",", -1).map(&:strip)
34
- raise ArgumentError, "列表不能有空项" if values.any?(&:empty?)
34
+ raise ArgumentError, "Lists must not contain empty entries" if values.any?(&:empty?)
35
35
  values
36
36
  end
37
37
  end
@@ -14,7 +14,7 @@ module CtxCli
14
14
 
15
15
  # 最后的错误流失败不能覆盖原本的失败或取消状态。
16
16
  def diagnostic(message, exception: nil)
17
- @error.puts(exception ? "#{message}(#{exception.class.name})" : message)
17
+ @error.puts(exception ? "#{message} (#{exception.class.name})" : message)
18
18
  rescue StandardError
19
19
  nil
20
20
  end
@@ -23,10 +23,10 @@ module CtxCli
23
23
  def run(argv)
24
24
  options = @command.parse(argv)
25
25
  return 0 if @command.informational?
26
- raise ArgumentError, "需要批次目录" unless options[:batch]
26
+ raise ArgumentError, "A batch directory is required" unless options[:batch]
27
27
  batch = File.expand_path(options[:batch])
28
28
  paths = source_paths(File.join(batch, @directory))
29
- raise ArgumentError, "无设备报告" if paths.empty?
29
+ raise ArgumentError, "No device reports found" if paths.empty?
30
30
  remaining = @max_total_bytes
31
31
  sources = paths.map do |path|
32
32
  raise ArgumentError, "review byte budget exceeded" unless remaining.positive?
@@ -38,10 +38,10 @@ module CtxCli
38
38
  @output.text(@adapter.publish(sources, options, batch: batch, context: context))
39
39
  0
40
40
  rescue Interrupt
41
- @output.diagnostic("建议生成已取消;已有报告保留,未执行设备变更。")
41
+ @output.diagnostic("Review cancelled; existing reports retained; no device changes executed.")
42
42
  130
43
43
  rescue StandardError => error
44
- @output.diagnostic("建议生成失败(#{error.class.name});请检查输入结构、目录权限和 --help;未执行设备变更。")
44
+ @output.diagnostic("Review failed (#{error.class.name}); check the input structure, directory permissions and --help; no device changes executed.")
45
45
  2
46
46
  end
47
47
 
@@ -21,7 +21,7 @@ module CtxCli
21
21
  return 0
22
22
  end
23
23
  return @commands.fetch(command).call(arguments) if @commands.key?(command)
24
- @error.puts "未知命令或多余参数;使用 #{@name} --help 查看用法"
24
+ @error.puts "Unknown command or extra arguments; use #{@name} --help for usage"
25
25
  2
26
26
  end
27
27
  end
@@ -10,7 +10,7 @@ module CtxCli
10
10
  def self.tty?(*streams) = streams.all? { |stream| stream.respond_to?(:tty?) && stream.tty? }
11
11
  def tty?(*others) = self.class.tty?(@input, @out, *others)
12
12
 
13
- def require_tty!(*others, message: "必须使用真实交互终端")
13
+ def require_tty!(*others, message: "An interactive terminal is required")
14
14
  raise ArgumentError, message unless tty?(*others)
15
15
  end
16
16
 
@@ -26,13 +26,13 @@ module CtxCli
26
26
  end
27
27
 
28
28
  def password(label:)
29
- raise ArgumentError, "密码提示需要交互终端;自动化任务可使用环境变量或 --stdin-credentials" unless self.class.tty?(@input)
29
+ raise ArgumentError, "Password prompts require an interactive terminal; use environment variables or --stdin-credentials for automation" unless self.class.tty?(@input)
30
30
  require "io/console"
31
- @out.print "#{label}:"
31
+ @out.print "#{label}: "
32
32
  @out.flush
33
33
  value = @input.noecho { |terminal| terminal.gets("\n") }&.chomp("\n")
34
34
  @out.puts
35
- raise ArgumentError, "密码不能为空" if value.nil? || value.empty?
35
+ raise ArgumentError, "Password must not be empty" if value.nil? || value.empty?
36
36
  value
37
37
  end
38
38
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CtxCli
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ctx_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gatework contributors
@@ -15,20 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '3.0'
19
- - - "<"
20
- - !ruby/object:Gem::Version
21
- version: '4'
18
+ version: '0'
22
19
  type: :runtime
23
20
  prerelease: false
24
21
  version_requirements: !ruby/object:Gem::Requirement
25
22
  requirements:
26
23
  - - ">="
27
24
  - !ruby/object:Gem::Version
28
- version: '3.0'
29
- - - "<"
30
- - !ruby/object:Gem::Version
31
- version: '4'
25
+ version: '0'
32
26
  - !ruby/object:Gem::Dependency
33
27
  name: fileutils
34
28
  requirement: !ruby/object:Gem::Requirement
@@ -123,6 +117,8 @@ files:
123
117
  - LICENSE
124
118
  - README.md
125
119
  - docs/migration.md
120
+ - docs/superpowers/plans/2026-10-24-configuration-source-paths.md
121
+ - docs/superpowers/specs/2026-10-24-configuration-source-paths.md
126
122
  - examples/configuration.rb
127
123
  - examples/offline.rb
128
124
  - lib/ctx_cli.rb