@renault-digital/bash-base 2.4.1 → 2.5.1

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.
@@ -0,0 +1,202 @@
1
+ # Bash and zsh compatibility
2
+
3
+ `bash-base` can be sourced by both Bash and zsh.
4
+
5
+ The project name and most executable examples remain Bash-oriented for backward
6
+ compatibility, but the reusable functions in `src/` are maintained within a
7
+ shared Bash/zsh contract.
8
+
9
+ ## Why this matters
10
+
11
+ A sourced file is interpreted by the **current shell**. Its shebang does not
12
+ start another shell.
13
+
14
+ For example, on current macOS systems the interactive shell is normally zsh:
15
+
16
+ ```text
17
+ zsh
18
+ ↓
19
+ source ~/.bash_profile
20
+ ↓
21
+ source bash-base
22
+ ↓
23
+ bash-base functions execute inside zsh
24
+ ```
25
+
26
+ Therefore a file named `bash-base`, or a source file beginning with
27
+ `#!/usr/bin/env bash`, can still fail when it is sourced by zsh if its function
28
+ bodies contain Bash-only constructs.
29
+
30
+ The compatibility work removes that accidental dependency while preserving the
31
+ existing Bash behavior and Bash ShellSpec regression suite.
32
+
33
+ ## Supported model
34
+
35
+ The production modules covered by the shared-shell contract are:
36
+
37
+ ```text
38
+ src/
39
+ alias.sh
40
+ argument.sh
41
+ array.sh
42
+ constant.sh
43
+ document.sh
44
+ out.sh
45
+ process.sh
46
+ reflection.sh
47
+ string.sh
48
+ tool.sh
49
+ ```
50
+
51
+ Executable scripts may still deliberately use:
52
+
53
+ ```bash
54
+ #!/usr/bin/env bash
55
+ ```
56
+
57
+ That is separate from the requirement that the reusable `src/` library can be
58
+ **sourced** by either Bash or zsh.
59
+
60
+ ## Compatibility rules
61
+
62
+ Production source should prefer the common Bash/zsh subset.
63
+
64
+ In particular:
65
+
66
+ - function declarations use `name() { ...; }`;
67
+ - prompts use `printf` plus `IFS= read -r`, not Bash `read -p` or zsh
68
+ `read "name?prompt"`;
69
+ - output that depends on escape interpretation uses `printf` instead of
70
+ implementation-dependent `echo -e`;
71
+ - Bash-only case conversion such as `${value^^}`, `${value,,}` and `${value^}`
72
+ is not used;
73
+ - Bash-only indirect array expansion such as `${!array}` is not used;
74
+ - library code does not require global `shopt` or `setopt` changes;
75
+ - shell-specific introspection is isolated behind an explicit
76
+ `BASH_VERSION`/`ZSH_VERSION` branch;
77
+ - data values are kept out of `eval` program text where practical.
78
+
79
+ ## Array portability boundary
80
+
81
+ Bash and zsh do not use the same native indexed-array model. Bash normally
82
+ starts indexed arrays at 0; zsh normally starts them at 1, and sparse-array
83
+ behavior also differs.
84
+
85
+ Logical operations therefore work on ordered values rather than assuming a
86
+ particular numeric index base. This applies to operations such as:
87
+
88
+ ```text
89
+ join
90
+ contains
91
+ sort
92
+ intersection
93
+ subtract
94
+ union
95
+ remove
96
+ map
97
+ filter
98
+ ```
99
+
100
+ `array_describe` / `array_from_describe` intentionally use each shell's native
101
+ `typeset -p` representation. Their supported contract is:
102
+
103
+ > serialize and restore in the same shell.
104
+
105
+ The serialized Bash sparse-array text is not promised to be a portable zsh
106
+ serialization, or vice versa.
107
+
108
+ ## Reflection boundary
109
+
110
+ Function and variable reflection is implemented explicitly per shell where
111
+ necessary:
112
+
113
+ ```text
114
+ Bash → compgen / typeset
115
+ zsh → functions / parameters tables
116
+ ```
117
+
118
+ The shell-specific zsh parameter-expansion syntax is kept inside the zsh branch
119
+ so that the complete source still parses in Bash.
120
+
121
+ ## Argument parsing and `$0`
122
+
123
+ zsh can expose a function-local value through `$0` while a shell function runs.
124
+ Where bash-base needs the original script identity, it uses `ZSH_ARGZERO` under
125
+ zsh and `$0` under Bash.
126
+
127
+ The compatibility test suite includes an actual script-level `args_parse -h`
128
+ case so rule discovery is tested in both shells, not only syntax-checked.
129
+
130
+ ## Test architecture
131
+
132
+ The existing ShellSpec suite remains the authoritative **Bash regression suite**.
133
+ It protects the historical Bash behavior and coverage.
134
+
135
+ A separate compatibility gate is implemented in:
136
+
137
+ ```text
138
+ scripts/test-shell-compat.sh
139
+ ```
140
+
141
+ The harness itself uses Bash but launches both Bash and zsh as test targets.
142
+ That avoids requiring the test runner itself to live in the common subset.
143
+
144
+ It verifies:
145
+
146
+ - `bash -n` for every shared source module;
147
+ - `zsh -n` for every shared source module;
148
+ - absence of known shell-specific constructs that the migration removed;
149
+ - common function declaration syntax;
150
+ - runtime behavior in Bash and zsh for strings, arrays, reflection, argument
151
+ parsing/validation, confirmations, and legacy compatibility names;
152
+ - script-level argument-rule discovery in both shells.
153
+
154
+ Run the shared-shell gate directly with:
155
+
156
+ ```bash
157
+ bash scripts/test-shell-compat.sh
158
+ ```
159
+
160
+ The normal local gate remains:
161
+
162
+ ```bash
163
+ npm test
164
+ ```
165
+
166
+ which runs:
167
+
168
+ ```text
169
+ scripts/test.sh
170
+ ↓
171
+ scripts/test-shell-compat.sh
172
+ ├── Bash compatibility/runtime
173
+ └── zsh compatibility/runtime
174
+ ↓
175
+ ShellSpec
176
+ └── existing Bash regression suite
177
+ ```
178
+
179
+ ## CI acceptance rule
180
+
181
+ GitHub Actions installs both Bash and zsh. A change is accepted only when both
182
+ of these layers pass:
183
+
184
+ ```text
185
+ Bash/zsh compatibility gate: PASS
186
+ Bash ShellSpec regression + coverage gate: PASS
187
+ ```
188
+
189
+ A syntax-only pass is not sufficient: several historical incompatibilities are
190
+ runtime differences, especially prompt handling, array indirection, case
191
+ conversion, reflection and `$0` semantics.
192
+
193
+ ## Contributing
194
+
195
+ When adding or changing a public helper:
196
+
197
+ 1. keep production syntax in the Bash/zsh common subset when possible;
198
+ 2. if behavior genuinely differs by shell, use a small explicit shell branch;
199
+ 3. add or update ShellSpec for Bash regression behavior;
200
+ 4. extend `scripts/test-shell-compat.sh` when the change depends on shell
201
+ semantics;
202
+ 5. run `npm test` before committing.
@@ -0,0 +1,289 @@
1
+ # Coverage collection, validation and reporting
2
+
3
+ ## What the percentage means
4
+
5
+ The authoritative gate is `coverage-macos` in
6
+ [the CI workflow](../.github/workflows/cicd.yml). It requires the union of the
7
+ measurable lines in `src/*.sh` to reach **100%**, using reports collected from
8
+ one checkout by [the strict verifier](../scripts/verify-final-coverage-v3.sh).
9
+
10
+ The runs are Bash 3.2, Homebrew Bash 5+, selected zsh ShellSpec tests, and an
11
+ independent Bash TTY probe. “Full report” describes a complete instrumentation
12
+ inventory; it does not mean that the entire ShellSpec suite runs under every
13
+ shell. The zsh test selection remains the selection in the verifier.
14
+
15
+ A line is covered when at least one of these runs records a positive hit for
16
+ that exact file and line. This is not per-shell 100% coverage, branch coverage,
17
+ all-input coverage, or proof of complete correctness. The denominator is taken
18
+ from the validated reports, not a fixed historical line count.
19
+
20
+ The separate Linux job continues to run compatibility/regression checks and
21
+ collect diagnostic reports. Its reports are retained as an artifact but are
22
+ not uploaded to Codecov or added to the strict macOS union.
23
+
24
+ ## CI test matrix
25
+
26
+ This matrix describes the commands configured in
27
+ [the workflow](../.github/workflows/cicd.yml) and
28
+ [the strict verifier](../scripts/verify-final-coverage-v3.sh), not the outcome of
29
+ an individual run. Read the logs for executed examples, skips, installed
30
+ versions, source SHA and results. Update this section when those commands change.
31
+
32
+ ### Operating system, shell and execution location
33
+
34
+ `ubuntu-22.04` and `macos-15` select the GitHub-hosted runner operating system.
35
+ Bash and zsh are the interpreters running on that operating system. These are
36
+ separate dimensions; macOS is not another name for zsh. The workflow's
37
+ `shell: bash` runs the orchestration step, while ShellSpec's `--shell` selects
38
+ the interpreter under test.
39
+
40
+ `coverage-macos` checks out source on GitHub's macOS runner, installs its tools,
41
+ and calls the verifier there. The verifier creates a fresh temporary report
42
+ directory on that machine. The workflow uploads that run's validated union;
43
+ it does not retrieve reports from a contributor's Mac. Running the verifier
44
+ on a development Mac produces a separate local result and does not itself run
45
+ the Codecov upload step.
46
+
47
+ Keep `macOS` in operating-system requirements, runner labels, platform-specific
48
+ notes and report-origin labels. Do not replace it globally with `zsh`. This
49
+ matrix keeps the existing job IDs and release dependencies unchanged.
50
+
51
+ ### Configured tests and report destinations
52
+
53
+ | Runner | Job | Interpreter or tool | Configured scope / entry point | Included in Codecov union? |
54
+ | --- | --- | --- | --- | --- |
55
+ | `ubuntu-22.04` | `test` | Installed Bash and zsh | `bash scripts/test.sh --kcov -f t`: the workflow's Bash/zsh compatibility entry point followed by the existing Bash ShellSpec regression suite. Compatibility cases are maintained by the test entry point, not redefined here. | No. Linux coverage remains diagnostic evidence. |
56
+ | `ubuntu-22.04` | `test` | Installed zsh, via ShellSpec `--shell zsh` | Selected `spec/string.spec.sh` and `spec/reflection.spec.sh` tests; separate `coverage-zsh` report. | No. Retained with Linux diagnostics. |
57
+ | `macos-15` | `coverage-macos` | `/bin/bash`, expected Bash 3.2 | ShellSpec over `spec/*.spec.sh`, subject to the suite's conditional skips; report label `bash32`. | Yes, as a full-inventory report. |
58
+ | `macos-15` | `coverage-macos` | Homebrew Bash, expected Bash 5+ | ShellSpec over `spec/*.spec.sh`, subject to the suite's conditional skips; report label `bash5`. | Yes, as a full-inventory report. |
59
+ | `macos-15` | `coverage-macos` | `/bin/zsh` | Four selected spec files listed below; report label `zsh`. This is not the complete ShellSpec suite. | Yes, as a full-inventory report. |
60
+ | `macos-15` | `coverage-macos` | Python PTY driver launching `/bin/bash` | `bash_base_tty_probe_v3.py --shell /bin/bash --preload-input`; the verifier requires `TTY_COVERAGE_PASS 2/2`. | Yes, as the supplemental TTY report. |
61
+ | `ubuntu-22.04` and `macos-15` | `test` and `coverage-macos`, respectively | Python 3 | `python3 -m unittest discover -s tests -p 'test_coverage_union_v3.py' -v`; synthetic report fixtures exercise validation and export. | No. These are tests of the coverage tooling, not additional production-source coverage. |
62
+
63
+ The macOS zsh selection is exactly:
64
+
65
+ ```text
66
+ spec/string.spec.sh
67
+ spec/reflection.spec.sh
68
+ spec/coverage_edges.spec.sh
69
+ spec/coverage_zsh_array_map.spec.sh
70
+ ```
71
+
72
+ The macOS job calls the strict verifier directly, not `scripts/test.sh`. Do not
73
+ infer that the separate Bash/zsh compatibility entry point is also invoked on
74
+ macOS merely because the strict verifier tests both interpreters.
75
+
76
+ The Linux zsh probe's **coverage report** is diagnostic. The probe itself is
77
+ not best-effort: the workflow propagates its test exit status and does not
78
+ ignore a failing probe.
79
+
80
+ ### Limits, versions and evidence
81
+
82
+ A **full-inventory report** contains the baseline measurable files and lines;
83
+ it need not execute every spec file or cover every measured line. An unexecuted
84
+ line remains in the inventory with zero hits. In particular, the zsh inventory
85
+ must match the Bash inventories even though the zsh spec selection is smaller.
86
+
87
+ The four macOS reports are collected sequentially from one checkout and merged
88
+ as described below. This is a documented testing matrix, not a
89
+ `strategy.matrix` expansion, a full operating-system-by-shell Cartesian product,
90
+ or an average of coverage percentages. Linux reports are not merged into it.
91
+
92
+ The configured checks do not establish a complete zsh ShellSpec run, an explicit
93
+ Linux Bash 3.2 job, TTY testing under every interpreter, or testing across every
94
+ macOS release and CPU architecture. A 100% union does not establish any of these
95
+ claims. The compatibility contract remains in
96
+ [Bash and zsh compatibility](bash-zsh-compatibility.md); this table records test
97
+ scope rather than expanding the public support contract.
98
+
99
+ ShellSpec is explicitly pinned to `0.28.1` in both collection jobs. Bash/zsh,
100
+ Homebrew packages and the runner images are not all pinned to immutable versions.
101
+ The verifier selects `/bin/bash`, `/bin/zsh` and `BASH5` (or Homebrew's Bash); its
102
+ labels are not substitutes for checking actual version output. It is configured
103
+ for the macOS tool layout, so running it unchanged on a different OS does not
104
+ prove that its `bash32` row actually used Bash 3.2.
105
+
106
+ For PRs, the strict job uses the PR head SHA and the Linux job uses the default
107
+ PR merge checkout. Keep their evidence separate. For a run-specific result,
108
+ record the source SHA, runner/image and architecture, actual interpreter versions,
109
+ executed/skipped tests and validated line totals alongside the CI result.
110
+ Historical local versions and line counts are not permanent CI guarantees.
111
+
112
+ Runner labels and workflow shell semantics are documented in GitHub's
113
+ [hosted-runner reference](https://docs.github.com/en/actions/reference/runners/github-hosted-runners)
114
+ and [workflow syntax](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax).
115
+
116
+ ## Validation and export
117
+
118
+ [bash_base_coverage_union_v3.py](../bash_base_coverage_union_v3.py) uses the first
119
+ full report as the measurable-line baseline. The three full reports must have
120
+ identical file and line inventories. The TTY supplement may omit whole files,
121
+ but a file present in that report must have the baseline's complete line
122
+ inventory. Additional or missing lines are not silently reconciled.
123
+
124
+ The verifier compares `src/*.sh` SHA-256 hashes before and after collection.
125
+ All test commands must succeed before it invokes the merger. Cobertura XML
126
+ alone cannot establish the source revision or prove that tests succeeded; do
127
+ not merge reports from unrelated source revisions or independent old runs.
128
+
129
+ The optional `--output-cobertura FILE` switch exports only the validated union.
130
+ It requires strict validation and `--source-dir` pointing to `src`. The output
131
+ must be outside `src` and must not already exist; use a fresh output path.
132
+
133
+ The exported report:
134
+
135
+ - preserves every baseline measurable line, including lines with zero hits;
136
+ - records union hits as `0` or `1`, not cumulative execution counts;
137
+ - uses repository-relative `src/<filename>.sh` paths and `<source>.</source>`;
138
+ - derives per-file and overall totals/rates from the same line records used by
139
+ the gate, with no asserted branch coverage.
140
+
141
+ The verifier writes the export to `$COVERAGE_ROOT/union/cobertura.xml`. Raw
142
+ reports, logs, source hashes, the tested commit and the export remain available
143
+ in the `strict-macos-coverage` artifact.
144
+
145
+ ### Failure behaviour
146
+
147
+ | Outcome | Union exported/upload eligible? | Strict gate |
148
+ | --- | --- | --- |
149
+ | Tests pass, source hashes unchanged, inventories valid, all lines covered | Yes | Pass |
150
+ | Same validation succeeds, but genuine uncovered lines remain | Yes, including all zero-hit lines | Fail |
151
+ | A test fails, source hashes differ, reports are missing/malformed, or inventories differ | No | Fail |
152
+ | The export cannot be written or the workflow cannot save its evidence | No upload | Fail |
153
+ | A Codecov upload fails after the gate passes | Existing export remains an artifact | The upload step makes `coverage-macos` fail |
154
+
155
+ Reporting a valid below-100% result is intentional: otherwise a coverage
156
+ regression could leave an old green percentage visible. The verifier still
157
+ returns nonzero and its failed step is not marked `continue-on-error`.
158
+
159
+ If no valid new upload exists, Codecov may continue to display an earlier
160
+ processed commit. Always compare commit SHAs and CI results, not only the badge.
161
+ An upload accepted by the client is not proof that server-side processing and
162
+ all UI widgets have finished updating.
163
+
164
+ ## Codecov upload and authentication
165
+
166
+ The workflow uploads exactly one report using the official Codecov action,
167
+ pinned to the v7.1.1 commit. `files` explicitly names the union XML and
168
+ `disable_search: true` prevents discovery of other reports. `plugins: noop`
169
+ disables additional collection plugins; `disable_file_fixes: true` avoids
170
+ client-side removal of measured lines. CLI integrity verification is not
171
+ bypassed. The action itself is pinned; its CLI download still uses the action's
172
+ default version policy, so this is not a claim that all transitive tools are
173
+ version-pinned.
174
+
175
+ Configure a **repository-specific Codecov upload token** as the GitHub Actions
176
+ secret `CODECOV_TOKEN`:
177
+
178
+ 1. In Codecov, open this repository's **Configuration / General** page and obtain
179
+ its repository upload token. Prefer this over an organization-wide token.
180
+ 2. In the GitHub repository, open **Settings → Secrets and variables → Actions**
181
+ and create or verify the repository secret named `CODECOV_TOKEN`.
182
+ 3. Set only the token value. Do not put credentials in `codecov.yml`, a commit,
183
+ a screenshot, or a shared log.
184
+
185
+ This credential is unrelated to `NPM_TOKEN`, `GITHUB_TOKEN`, Docker credentials,
186
+ or npm Trusted Publishing. The coverage job only requests `contents: read`;
187
+ it does not gain `id-token: write`. The two npm OIDC release jobs are unchanged.
188
+
189
+ Public fork PRs do not receive repository secrets. The official action supports
190
+ public fork uploads without the upstream token. Do not enable
191
+ `pull_request_target` to run untrusted PR code with secrets. For Dependabot PRs,
192
+ configure the upload token as a Dependabot secret as described by Codecov's
193
+ action documentation. Actual upload acceptance remains subject to Codecov's
194
+ repository/organization settings and service availability.
195
+
196
+ The action also supports Codecov OIDC, but this patch deliberately does not add
197
+ OIDC permission to test jobs in the existing npm publishing workflow. A future
198
+ OIDC change should review the identity trust boundary separately.
199
+
200
+ ## Commit identity and release boundary
201
+
202
+ For pull requests, `coverage-macos` checks out the PR head SHA explicitly and
203
+ passes the actual `git rev-parse HEAD` value to Codecov as `override_commit`.
204
+ The separate Linux job continues to test the default PR merge checkout. These
205
+ are distinct checks; they are not presented as reports from the same checkout.
206
+ For push/manual runs, coverage uses the triggering SHA.
207
+
208
+ Do not label a feature-branch upload as `master` or relabel a merge-commit report
209
+ as a different source SHA. The default-branch README badge will not change just
210
+ because a PR's coverage passes.
211
+
212
+ The `semantic-release-preview` job still depends on `coverage-macos`, and the
213
+ real release job still depends on the preview. Thus either the strict gate or
214
+ its Codecov upload failing blocks the existing release dependency chain. A
215
+ Codecov outage can therefore block publishing even when the local gate passes;
216
+ this is an explicit consequence of `fail_ci_if_error: true`.
217
+
218
+ The workflow already supports `workflow_dispatch`, with both release jobs
219
+ restricted to `push` on `master`. A manual run of this workflow on the proposed
220
+ branch can validate coverage/upload without invoking those release jobs.
221
+ Merging to `master` is not a side-effect-free upload test: it can trigger the
222
+ existing npm/GitHub/Docker release path.
223
+
224
+ ## Why codecov.yml is present
225
+
226
+ A repository-level Codecov YAML is optional. Without one, Codecov uses its
227
+ service defaults and any organization-level settings; its absence did not
228
+ cause the missing union upload.
229
+
230
+ The [minimal project configuration](../codecov.yml) makes the project and patch
231
+ coverage targets explicitly 100%, with zero tolerance and no carryforward for
232
+ `strict-union`. It adds no ignore patterns or source-path filters. This is a
233
+ reporting policy, not a replacement for the source inventory validator.
234
+
235
+ `require_ci_to_pass: false` and `wait_for_ci: false` allow Codecov notifications
236
+ to report genuine regressions even when CI is already red, or before unrelated
237
+ release steps finish. They do **not** make a failed strict gate successful or
238
+ change GitHub branch protection. There is one authoritative upload, so
239
+ `after_n_builds` is 1, not 4.
240
+
241
+ Codecov merges repository YAML with organization YAML; account-level settings
242
+ and service-side processing must still be checked in the first real run.
243
+ Validate the YAML with Codecov's official validator before rollout.
244
+
245
+ ## README display
246
+
247
+ The tree remains clickable and points to the `master/src` detail view. A brief
248
+ caption explains union line coverage. The badge remains a dynamic Codecov
249
+ badge; it is never replaced with a static “100%”. Historical commits keep their
250
+ historical coverage unless new reports for those commits are intentionally
251
+ submitted.
252
+
253
+ The GitHub Actions badge uses Shields' supported workflow-filename endpoint,
254
+ with `cicd.yml`, `branch=master` and `event=push`. It reports the **whole
255
+ workflow**, not just `coverage-macos`. A release failure may make that badge red
256
+ while Codecov shows full line coverage.
257
+
258
+ ## Verification and troubleshooting
259
+
260
+ Run the exporter self-tests (synthetic report fixtures, not production coverage):
261
+
262
+ ```bash
263
+ python3 -m unittest discover -s tests -p 'test_coverage_union_v3.py' -v
264
+ ```
265
+
266
+ Run the actual coverage gate on the configured macOS development environment:
267
+
268
+ ```bash
269
+ bash scripts/verify-final-coverage-v3.sh "$PWD"
270
+ ```
271
+
272
+ In the CI log, inspect `SOURCE_COMMIT`, `SOURCE_SHA256_UNCHANGED`,
273
+ `MEASURABLE_LINE_SETS`, `VALIDATED_COBERTURA` and `COVERAGE_GATE`. Open the artifact
274
+ and compare the XML's `lines-valid` / `lines-covered` counts and `src/*.sh`
275
+ filenames with the merger log and Codecov for that same commit.
276
+
277
+ When Codecov disagrees, check in this order: commit/branch identity; uploader
278
+ success and server processing; source paths and measured-line counts; any other
279
+ uploaders or organization-level YAML. Do not lower the gate, hard-code a badge,
280
+ add ignore rules, or rerun an obsolete uploader to make the display green.
281
+
282
+ ## Official references
283
+
284
+ - [Codecov YAML and its optional status](https://docs.codecov.com/docs/codecov-yaml)
285
+ - [Codecov YAML reference](https://docs.codecov.com/docs/codecovyml-reference)
286
+ - [Codecov upload tokens and public fork behaviour](https://docs.codecov.com/docs/codecov-tokens)
287
+ - [Pinned Codecov action inputs](https://github.com/codecov/codecov-action/blob/303a32d7a59b442fa8d48b6a1cc6825c09c847a5/action.yml)
288
+ - [Codecov report fixes](https://docs.codecov.com/docs/fixing-reports)
289
+ - [GitHub Actions workflow status badge](https://shields.io/badges/git-hub-actions-workflow-status)
@@ -0,0 +1,176 @@
1
+ # Bash-base design principles
2
+
3
+ ## Purpose and status
4
+
5
+ Bash-base is a Shell function library for composing system commands and
6
+ expressing automation logic in Bash and zsh. Its purpose is to reduce repeated
7
+ research, implementation and verification work through reusable higher-level
8
+ interfaces.
9
+
10
+ The central question is:
11
+
12
+ > Does this change make command-oriented Shell programming easier to understand,
13
+ > combine and maintain, while reducing recurring implementation effort or risk?
14
+
15
+ This document guides maintenance, feature review and future API design. It does
16
+ not retroactively redefine existing APIs, certify arbitrary inputs as safe, or
17
+ announce unimplemented features. Existing behavior must be checked against the
18
+ [API reference](references.md), the relevant source comments and tests, and the
19
+ [Bash/zsh compatibility contract](bash-zsh-compatibility.md). Conflicts between
20
+ these sources require investigation rather than an assumption that one silently
21
+ changes another.
22
+
23
+ ## 1. Start from a real pain point
24
+
25
+ A feature should address a concrete scripting problem that recurs or is costly
26
+ to get right. Time spent searching, comparing approaches, checking edge cases
27
+ and maintaining duplicated implementations is part of that cost.
28
+
29
+ Code length alone is not a measure of value. A small wrapper may be worthwhile
30
+ when it provides a useful contract or isolates a difficult compatibility issue.
31
+ Conversely, a large implementation is not justified simply because another
32
+ language or library provides the same feature.
33
+
34
+ Consider built-ins, existing Bash-base helpers, established external tools,
35
+ documentation improvements and examples before adding a new public API.
36
+ Personal or internal use cases are valid evidence; widespread adoption is not a
37
+ prerequisite for proposing a useful change.
38
+
39
+ ## 2. Use abstractions to support command composition
40
+
41
+ Higher-level interfaces should help callers organize arguments, transform
42
+ command outputs and express automation logic without repeatedly implementing
43
+ Shell-specific mechanics.
44
+
45
+ String and array operations serve that purpose. List, Set and Map are useful
46
+ concepts for reasoning about order, duplicates and key/value relationships, but
47
+ mentioning them here does not establish separate implemented APIs. Any extension
48
+ must first justify its role in real command-oriented workflows.
49
+
50
+ The goal is not a complete object system, a replacement Shell interpreter, a
51
+ cloud SDK collection, or a general-purpose application framework. Prefer
52
+ integration with suitable tools over duplicating them without a clear benefit.
53
+
54
+ ## 3. Define behavior before adding an interface
55
+
56
+ For a new public helper, or a deliberate contract change, document the relevant
57
+ parts of the following before accepting the implementation:
58
+
59
+ | Area | Questions to resolve |
60
+ | --- | --- |
61
+ | Inputs | Arguments, stdin or named variables? Required arguments? Accepted data, code and identifier forms? |
62
+ | Outputs | stdout, named variables or mutation? Output format and record boundaries? |
63
+ | Outcomes | Success, an empty result, a false predicate, invalid input and execution failure: how are they distinguished? |
64
+ | Side effects | Can the helper prompt, execute commands, change files, affect processes, mutate variables or alter Shell state? |
65
+ | Data semantics | Are order, duplicates, empty elements, whitespace, newlines and trailing newlines preserved? What is unsupported? |
66
+ | Environment | Which Shell versions, options, locales and external-command variants are supported? |
67
+ | Composition | How does the result feed another helper or command, and how is failure propagated? |
68
+
69
+ Do not impose a new universal exit-code table on existing functions without a
70
+ compatibility review. Similarly, changing an output channel, ordering rule or
71
+ empty-value representation can break callers even when the function name stays
72
+ the same.
73
+
74
+ ## 4. Preserve meaningful boundaries
75
+
76
+ Composable interfaces must keep the distinction between data and executable
77
+ Shell code explicit. A helper that intentionally accepts trusted code must say
78
+ so; it must not be presented as an arbitrary-data interface.
79
+
80
+ For new or reviewed interfaces, examine argument boundaries, variable-name
81
+ validation, quoting, output framing and error propagation. A convenient wrapper
82
+ must not hide unexpected prompting, discarded failures or changes to the
83
+ caller's environment.
84
+
85
+ Do not describe newline-separated output as lossless for arbitrary records
86
+ unless the input domain or an encoding makes that true. A text representation,
87
+ a native array declaration and a portable interchange format are different
88
+ contracts. Serialization must state whether it supports restoration within one
89
+ Shell, cross-Shell exchange, or both. Follow the existing compatibility contract;
90
+ this design document does not broaden any serialization promise.
91
+
92
+ Diagnostic output, normal data output and presentation-oriented helpers also
93
+ need distinct contracts. Changing an existing helper's stdout/stderr behavior
94
+ requires review, not merely a new general rule in this document.
95
+
96
+ ## 5. Make portability an explicit boundary
97
+
98
+ Target the documented Bash/zsh environment, not every implementation of Shell.
99
+ Use common syntax where practical and isolate necessary Shell-specific behavior
100
+ behind small, explicit implementation branches.
101
+
102
+ The public behavior should be consistent within the declared boundary. Where a
103
+ native representation or environment-dependent operation is intentionally
104
+ Shell-specific, document that exception and test it accordingly.
105
+
106
+ A sourced library must not acquire undocumented dependencies on caller options
107
+ or unexpectedly change `IFS`, options, traps, the working directory or global
108
+ variables. Audit existing behavior before changing it; new APIs should make
109
+ required effects explicit and avoid unrelated ones.
110
+
111
+ ## 6. Support reliability with evidence
112
+
113
+ Keep the existing Bash regression tests, Bash/zsh compatibility checks and
114
+ mandatory coverage gate. Add tests relevant to the behavior changed, including
115
+ error paths and boundary inputs rather than only typical examples.
116
+
117
+ The 100% requirement concerns measurable source lines under the project's
118
+ required coverage configuration. A passing report from one environment does not
119
+ replace the required gate. Cover relevant supported-Shell and interactive or
120
+ non-interactive behavior; coverage is not proof of complete branch coverage, all
121
+ input combinations or the absence of security defects.
122
+
123
+ Documentation-only changes do not justify artificial test changes. Validate the
124
+ changed documentation or metadata, explain why executable behavior is unchanged,
125
+ and keep all required status checks and coverage gates in force.
126
+
127
+ For an implementation review, identify the tested environment and the evidence
128
+ that supports each portability or safety claim. Do not weaken tests, exclusions
129
+ or coverage requirements merely to obtain a passing result.
130
+
131
+ ## 7. Keep installation and execution predictable
132
+
133
+ Favor fixed-version, local loading for reproducible or offline workflows. Make
134
+ network access and update behavior clear for every documented installation or
135
+ loading method rather than treating all methods as equivalent.
136
+
137
+ The existing build already generates the single-file `bin/bash-base` artifact.
138
+ Its presence does not by itself prove that every installation method is offline
139
+ or that every published version contains the same compatibility work as the
140
+ current source branch.
141
+
142
+ Distinguish installation and build tooling from commands needed by a helper at
143
+ runtime. Offline loading is not a claim that the application needs no external
144
+ utilities or network access of its own. Declare dependencies and prefer explicit
145
+ user control over updates.
146
+
147
+ ## 8. Evolve without silently breaking callers
148
+
149
+ Preserve existing public behavior unless a change is deliberately reviewed,
150
+ documented and released under the project's compatibility and versioning rules.
151
+ New wrappers, compatibility layers or parallel interfaces may provide a migration
152
+ path, but they also have maintenance costs and should not be added automatically.
153
+
154
+ When a legacy interface conflicts with a desired design principle, record its
155
+ actual behavior and relevant tests first. Distinguish a defect fix from an
156
+ intentional API redesign. A new principle is not permission for an unrelated
157
+ behavior change, rename, execution-mode change or dependency upgrade.
158
+
159
+ ## Feature review and acceptance
160
+
161
+ Early proposals may start with a use case and an unresolved problem. Before an
162
+ implementation is merged, the contributor and maintainer should establish the
163
+ need, alternatives, scope, public contract, test evidence and compatibility
164
+ impact described in [CONTRIBUTING](../CONTRIBUTING.md).
165
+
166
+ The core review dimensions are **Pain-driven, Contract-driven and
167
+ Evidence-driven**. A feature should save more research, implementation or
168
+ maintenance work than it introduces, with an understandable learning cost.
169
+ There is no function-count target and no requirement to add a whole family of
170
+ APIs when only one well-defined operation is needed.
171
+
172
+ Keep this document focused on design decisions. The API reference belongs to
173
+ the existing source-comment generation workflow; specific enhancement proposals
174
+ and scheduling decisions belong in issues and pull requests. The
175
+ [roadmap](roadmap.md) tracks possible future directions and their acceptance
176
+ criteria; it does not override existing API contracts or these principles.