mapterrain 0.1.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/LICENSE ADDED
@@ -0,0 +1,191 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to the Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by the Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding any notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ Copyright 2024-2026 PMcL
180
+
181
+ Licensed under the Apache License, Version 2.0 (the "License");
182
+ you may not use this file except in compliance with the License.
183
+ You may obtain a copy of the License at
184
+
185
+ http://www.apache.org/licenses/LICENSE-2.0
186
+
187
+ Unless required by applicable law or agreed to in writing, software
188
+ distributed under the License is distributed on an "AS IS" BASIS,
189
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190
+ See the License for the specific language governing permissions and
191
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,467 @@
1
+ # Terrain
2
+
3
+ **Map your test terrain.** Understand your test system in 30 seconds.
4
+
5
+ ```bash
6
+ # Homebrew
7
+ brew install pmclSF/terrain/mapterrain
8
+
9
+ # npm
10
+ npm install -g mapterrain
11
+
12
+ cd your-repo
13
+ terrain analyze
14
+ ```
15
+
16
+ That's it. No config, no setup, no test execution required.
17
+
18
+ > **New here?** Read the [Quickstart Guide](docs/quickstart.md) to understand your first report in 5 minutes.
19
+
20
+ ---
21
+
22
+ Terrain is a test system intelligence platform. It reads your repository — test code, source structure, coverage data, runtime artifacts, ownership files, and local policy — and builds a structural model of how your tests relate to your code. From that model it surfaces risk, quality gaps, redundancy, fragile dependencies, and migration readiness, all without running a single test.
23
+
24
+ The core idea: every codebase has a *test terrain* — the shape of its testing infrastructure, the density of coverage across areas, the hidden fault lines where a fixture change cascades into thousands of tests. Terrain makes that shape visible and navigable so you can make informed decisions about what to test, what to fix, and where to invest.
25
+
26
+ ## What "Test Terrain" Means
27
+
28
+ Most teams know what tests they have. Few teams understand the *terrain* underneath:
29
+
30
+ - Which source files have no structural test coverage?
31
+ - Which shared fixtures fan out to thousands of tests, making any change to them a blast-radius problem?
32
+ - Which test clusters are near-duplicates burning CI time?
33
+ - Which areas have tests but weak assertions that wouldn't catch a real regression?
34
+ - When you change `auth/session.ts`, which 41 of your 18,000 tests actually matter?
35
+
36
+ Test terrain is the structural topology of your test system — the dependency graph, the coverage landscape, the duplication clusters, the fanout hotspots, the skip debt. Terrain maps it.
37
+
38
+ ## Problems Terrain Solves
39
+
40
+ **"We don't know the state of our test system."** Teams inherit test suites they didn't write. Terrain gives a baseline in seconds: framework mix, coverage confidence, duplication, risk posture.
41
+
42
+ **"CI takes too long and we don't know what to cut."** Terrain identifies redundant tests, high-fanout fixtures, and confidence-based test selection — showing where CI time is wasted and what can be safely reduced.
43
+
44
+ **"We changed auth code — what tests should we worry about?"** Terrain traces your change through the import graph and structural dependencies, returning the impacted tests ranked by confidence, with reason chains explaining each selection.
45
+
46
+ **"A tool flagged something but won't explain why."** Every Terrain finding carries an evidence chain. `terrain explain` shows exactly what signals, dependency paths, and scoring rules produced each decision.
47
+
48
+ **"We're migrating frameworks and need to know what's blocking us."** Migration readiness, blockers by type, and preview-scoped difficulty assessment — all derived from static analysis.
49
+
50
+ ## The Four Canonical Workflows
51
+
52
+ Terrain is organized around four questions. Everything else is a supporting view.
53
+
54
+ ```
55
+ terrain analyze "What is the state of our test system?"
56
+ terrain insights "What should we fix in our test system?"
57
+ terrain impact "What validations matter for this change?"
58
+ terrain explain "Why did Terrain make this decision?"
59
+ ```
60
+
61
+ ### 1. Analyze — understand the test system
62
+
63
+ ```bash
64
+ terrain analyze
65
+ ```
66
+
67
+ ```
68
+ Terrain — Test Suite Analysis
69
+ ============================================================
70
+
71
+ conftest.py fixture fans out to 3,100 tests — any change retriggers the frame/ suite.
72
+
73
+ Key Findings
74
+ ------------------------------------------------------------
75
+ 1. [HIGH] 23 source files (18%) have low structural coverage
76
+ 2. [HIGH] 8 duplicate test clusters with 0.91+ similarity
77
+ 3. [MEDIUM] 34 xfail markers older than 180 days
78
+
79
+ Repository Profile
80
+ ------------------------------------------------------------
81
+ Test volume: very large
82
+ CI pressure: high
83
+ Coverage confidence: medium
84
+ Redundancy level: medium
85
+ Fanout burden: high
86
+
87
+ Validation Inventory
88
+ ------------------------------------------------------------
89
+ Test files: 1,047
90
+ Test cases: 52,341
91
+ Frameworks:
92
+ pytest 1,047 files [unit]
93
+
94
+ Risk Posture
95
+ ------------------------------------------------------------
96
+ health: MODERATE
97
+ coverage_depth: ELEVATED
98
+ coverage_diversity: STRONG
99
+ structural_risk: STRONG
100
+ operational_risk: STRONG
101
+
102
+ Next steps:
103
+ terrain insights prioritized actions and recommendations
104
+ terrain impact what tests matter for this change?
105
+ ```
106
+
107
+ ### 2. Insights — find what to improve
108
+
109
+ ```bash
110
+ terrain insights
111
+ ```
112
+
113
+ ```
114
+ Terrain — Test System Health Report
115
+ ============================================================
116
+
117
+ Health Grade: C
118
+
119
+ Reliability Problems (2)
120
+ [HIGH] 12 flaky tests with >10% failure rate
121
+ [MEDIUM] 34 skipped tests consuming CI resources
122
+
123
+ Coverage Debt (2)
124
+ [HIGH] 23 source files (18%) have low structural coverage
125
+ [MEDIUM] conftest.py fixture fans out to 3,100 tests
126
+
127
+ Recommended Actions
128
+ 1. [reliability] Quarantine 12 flaky tests
129
+ why: Flaky tests block unrelated PRs and erode CI trust.
130
+ 2. [coverage] Add tests for 23 uncovered source files
131
+ why: Changes in uncovered files cannot trigger test selection.
132
+ 3. [optimization] Consolidate 8 duplicate test clusters
133
+ why: 0.91+ similarity — redundant CI cost with no coverage benefit.
134
+ ```
135
+
136
+ ### 3. Impact — understand what a change affects
137
+
138
+ ```bash
139
+ terrain impact --base main
140
+ ```
141
+
142
+ ```
143
+ Terrain Impact Analysis
144
+ ============================================================
145
+
146
+ Summary: 3 file(s) changed, 41 test(s) relevant. Posture: needs_attention.
147
+
148
+ Impacted tests: 127 of 52,341 total
149
+ Coverage confidence: High
150
+
151
+ Recommended Tests (41)
152
+ ------------------------------------------------------------
153
+ tests/groupby/test_groupby.py [exact]
154
+ Covers: groupby.py:GroupBy.aggregate
155
+ tests/groupby/test_apply.py [exact]
156
+ Covers: groupby.py:GroupBy.apply
157
+ tests/resample/test_base.py [inferred]
158
+ Reached via shared fixture path
159
+ ...and 38 more
160
+
161
+ Protection Gaps
162
+ ------------------------------------------------------------
163
+ [medium] pandas/core/groupby/ops.py — no covering tests found
164
+
165
+ Next steps:
166
+ terrain impact --show tests full test list
167
+ terrain impact --show gaps all protection gaps
168
+ ```
169
+
170
+ ### 4. Explain — understand why
171
+
172
+ ```bash
173
+ terrain explain tests/io/json/test_pandas.py
174
+ ```
175
+
176
+ ```
177
+ Test File: tests/io/json/test_pandas.py
178
+ Framework: pytest
179
+ Tests: 84 Assertions: 312
180
+
181
+ Signals (3):
182
+ [high] networkDependency: 12 tests use @pytest.mark.network — flaky in CI
183
+ [medium] weakAssertion: 8 bare assert statements without descriptive messages
184
+ [low] xfailAccumulation: 3 xfail markers older than 180 days
185
+
186
+ Next steps:
187
+ terrain explain selection explain overall test selection strategy
188
+ terrain impact --show tests see all impacted tests
189
+ ```
190
+
191
+ See [Canonical User Journeys](docs/product/canonical-user-journeys.md) for the full workflow specification and [example outputs](docs/examples/) for detailed report samples.
192
+
193
+ ## Product Philosophy
194
+
195
+ **Inference first.** Terrain reads code. It parses imports, detects frameworks, resolves coverage relationships, and builds dependency graphs from what already exists in the repository. No annotations, no test tagging, no SDK integration required.
196
+
197
+ **Zero-config by default.** `terrain analyze` works on any repository with test files. Coverage data, runtime artifacts, ownership files, and policy rules are optional inputs that enrich the model — but the core analysis requires nothing beyond the code itself.
198
+
199
+ **Explainability over magic.** Every finding carries evidence: which signal type, what confidence level, what dependency path, what scoring rule. `terrain explain` exposes the full reasoning chain behind any decision. Teams should never wonder *why* Terrain said something.
200
+
201
+ **Conservative under uncertainty.** When Terrain encounters ambiguity — a dependency path with low confidence, a file that might or might not be a test — it flags the uncertainty rather than guessing. Impact analysis uses fallback policies with explicit confidence penalties rather than silently expanding scope.
202
+
203
+ **System health, not individual productivity.** Terrain measures the test system. It never attributes quality to individual developers. Ownership information is used for routing and triage, not scoring.
204
+
205
+ ## Who Uses Terrain
206
+
207
+ Terrain is framework-agnostic and language-aware. The same analysis model applies across:
208
+
209
+ - **Frontend teams** — React/Vue component tests, Playwright/Cypress E2E suites, Vitest/Jest unit tests
210
+ - **Backend teams** — Go test suites, pytest collections, JUnit hierarchies, integration test infrastructure
211
+ - **Mobile teams** — cross-platform test suites with standard test frameworks
212
+ - **QA / SDET** — test portfolio management, coverage gap analysis, migration planning across frameworks
213
+ - **SRE / Platform** — CI optimization, test selection for pipelines, policy enforcement
214
+ - **AI / ML evaluation** — evaluation suite structure, benchmark test management, coverage across model behaviors
215
+
216
+ The structural model is the same. The signals and recommendations adapt to the framework and test patterns detected.
217
+
218
+ ## How CI Optimization Emerges
219
+
220
+ Terrain does not start with CI optimization. It starts with understanding.
221
+
222
+ When you run `terrain analyze`, Terrain builds a structural model: which tests exist, which source files they cover, how they depend on shared fixtures, where duplication lives. From that model, CI optimization *emerges*:
223
+
224
+ - **Test selection** — `terrain impact` traces a diff through the dependency graph and returns only the tests that structurally matter, ranked by confidence. This is not a heuristic skip list — it is a graph traversal with evidence.
225
+ - **Redundancy reduction** — `terrain insights` surfaces duplicate test clusters. Removing or consolidating them directly reduces CI time without reducing coverage.
226
+ - **Fanout control** — High-fanout fixtures that trigger thousands of tests on any change are identified and prioritized for splitting.
227
+ - **Confidence-based runs** — Impact analysis assigns confidence scores. CI pipelines can run high-confidence tests immediately and defer low-confidence tests to nightly runs.
228
+
229
+ The result is faster CI that comes from *understanding the test system*, not from skipping tests and hoping for the best.
230
+
231
+ ## Installation
232
+
233
+ ### Homebrew (macOS and Linux)
234
+
235
+ ```bash
236
+ brew install pmclSF/terrain/mapterrain
237
+ ```
238
+
239
+ After the first install, you can also tap once and use the short formula name:
240
+
241
+ ```bash
242
+ brew tap pmclSF/terrain
243
+ brew install mapterrain
244
+ ```
245
+
246
+ ### npm
247
+
248
+ ```bash
249
+ npm install -g mapterrain
250
+ ```
251
+
252
+ ### Go install
253
+
254
+ ```bash
255
+ go install github.com/pmclSF/terrain/cmd/terrain@latest
256
+ ```
257
+
258
+ Requires Go 1.23 or later.
259
+
260
+ ### Pre-built binaries
261
+
262
+ Download the appropriate binary for your platform from [GitHub Releases](https://github.com/pmclSF/terrain/releases), then:
263
+
264
+ ```bash
265
+ chmod +x terrain
266
+ sudo mv terrain /usr/local/bin/
267
+ ```
268
+
269
+ Binaries are available for macOS, Linux, and Windows (amd64 and arm64).
270
+
271
+ ### Build from source
272
+
273
+ ```bash
274
+ git clone https://github.com/pmclSF/terrain.git
275
+ cd terrain
276
+ go build -o terrain ./cmd/terrain
277
+ ```
278
+
279
+ ### Verify installation
280
+
281
+ ```bash
282
+ terrain --version
283
+ ```
284
+
285
+ ## Quick Start
286
+
287
+ ```bash
288
+ # Detect coverage/runtime data paths (recommended first step)
289
+ terrain init
290
+
291
+ # Analyze the current repository
292
+ terrain analyze
293
+
294
+ # JSON output for any command
295
+ terrain analyze --json
296
+
297
+ # See what a change affects
298
+ terrain impact --base main
299
+
300
+ # Get prioritized recommendations
301
+ terrain insights
302
+ ```
303
+
304
+ ## Commands
305
+
306
+ ### Primary commands
307
+
308
+ | Command | Question |
309
+ |---------|----------|
310
+ | `terrain analyze` | What is the state of our test system? |
311
+ | `terrain insights` | What should we fix in our test system? |
312
+ | `terrain impact` | What validations matter for this change? |
313
+ | `terrain explain <target>` | Why did Terrain make this decision? |
314
+
315
+ ### Supporting commands
316
+
317
+ | Command | Purpose |
318
+ |---------|---------|
319
+ | `terrain init` | Detect data files and print recommended analyze command |
320
+ | `terrain summary` | Executive summary with risk, trends, benchmark readiness |
321
+ | `terrain focus` | Prioritized next actions |
322
+ | `terrain posture` | Detailed posture breakdown with measurement evidence |
323
+ | `terrain portfolio` | Portfolio intelligence: cost, breadth, leverage, redundancy |
324
+ | `terrain metrics` | Aggregate metrics scorecard |
325
+ | `terrain compare` | Compare two snapshots for trend tracking |
326
+ | `terrain select-tests` | Recommend protective test set for a change |
327
+ | `terrain pr` | PR/change-scoped analysis |
328
+ | `terrain show <type> <id>` | Drill into test, unit, owner, or finding |
329
+ | `terrain migration <sub>` | Migration readiness, blockers, or preview |
330
+ | `terrain policy check` | Evaluate local policy rules |
331
+ | `terrain export benchmark` | Privacy-safe JSON export for benchmarking |
332
+ | `terrain serve` | Local HTTP server with HTML report and JSON API |
333
+
334
+ ### AI / eval
335
+
336
+ | Command | Purpose |
337
+ |---------|---------|
338
+ | `terrain ai list` | List detected scenarios, prompts, datasets, eval files |
339
+ | `terrain ai doctor` | Validate AI/eval setup and surface configuration issues |
340
+ | `terrain ai run` | Execute eval scenarios and collect results |
341
+ | `terrain ai replay` | Replay and verify a previous eval run artifact |
342
+ | `terrain ai record` | Record eval results as a baseline snapshot |
343
+ | `terrain ai baseline` | Manage eval baselines (show, compare) |
344
+
345
+ ### Conversion / migration
346
+
347
+ | Command | Purpose |
348
+ |---------|---------|
349
+ | `terrain convert <source>` | Go-native test conversion (25 directions) |
350
+ | `terrain convert-config <source>` | Convert framework config files |
351
+ | `terrain migrate <dir>` | Project-wide migration with state tracking |
352
+ | `terrain estimate <dir>` | Estimate migration complexity |
353
+ | `terrain status` | Show migration progress |
354
+ | `terrain checklist` | Generate migration checklist |
355
+ | `terrain doctor [path]` | Run migration diagnostics |
356
+ | `terrain reset` | Clear migration state |
357
+ | `terrain list-conversions` | List supported conversion directions |
358
+ | `terrain shorthands` | List shorthand aliases (e.g., `cy2pw`, `jest2vt`) |
359
+ | `terrain detect <file-or-dir>` | Detect dominant framework |
360
+
361
+ ### Advanced / debug
362
+
363
+ | Command | Purpose |
364
+ |---------|---------|
365
+ | `terrain debug graph` | Dependency graph statistics |
366
+ | `terrain debug coverage` | Structural coverage analysis |
367
+ | `terrain debug fanout` | High-fanout node analysis |
368
+ | `terrain debug duplicates` | Duplicate test cluster analysis |
369
+ | `terrain debug depgraph` | Full dependency graph analysis (all engines) |
370
+
371
+ Repository-scoped commands support `--root PATH`, and machine-readable commands support `--json`. Most analysis commands support `--verbose` for additional detail. Run `terrain <command> --help` for full flag documentation.
372
+
373
+ ## Architecture Overview
374
+
375
+ Terrain is built around a signal-first architecture:
376
+
377
+ ```
378
+ Repository scan → Signal detection → Risk modeling → Reporting
379
+ │ │ │ │
380
+ test files framework-specific explainable human-readable
381
+ source files pattern detectors risk scoring + JSON output
382
+ coverage data quality signals with evidence
383
+ runtime artifacts health signals chains
384
+ ownership files migration signals
385
+ policy rules governance signals
386
+ ```
387
+
388
+ - **Signals** are the core abstraction — every finding is a structured signal with type, severity, confidence, evidence, and location
389
+ - **Snapshots** (`TestSuiteSnapshot`) are the canonical serialized artifact — the complete structural model of a test system at a point in time
390
+ - **Risk surfaces** are derived from signals with explainable scoring across dimensions (quality, reliability, speed, governance)
391
+ - **Dependency graphs** model import relationships, fixture fanout, and structural coverage
392
+ - **Reports** synthesize signals, risk, trends, and benchmark readiness into actionable output
393
+
394
+ ```
395
+ cmd/terrain/ CLI (30+ commands)
396
+ internal/ 47 Go packages covering analysis, signals, risk,
397
+ impact, depgraph, measurement, reporting, and more
398
+ ```
399
+
400
+ See [DESIGN.md](DESIGN.md) for the full architecture overview and package map, [docs/architecture/](docs/architecture/) for detailed design documents, and [docs/json-schema.md](docs/json-schema.md) for JSON output structure.
401
+
402
+ ## Snapshot Workflow
403
+
404
+ Terrain supports local snapshot history for trend tracking:
405
+
406
+ ```bash
407
+ # Save a snapshot
408
+ terrain analyze --write-snapshot
409
+
410
+ # Later, save another snapshot
411
+ terrain analyze --write-snapshot
412
+
413
+ # Compare the two most recent snapshots
414
+ terrain compare
415
+
416
+ # Executive summary automatically includes trend highlights
417
+ terrain summary
418
+ ```
419
+
420
+ Snapshots are stored in `.terrain/snapshots/` as timestamped JSON files.
421
+
422
+ ## Policy
423
+
424
+ Define local policy rules in `.terrain/policy.yaml`:
425
+
426
+ ```yaml
427
+ rules:
428
+ disallow_skipped_tests: true
429
+ max_weak_assertions: 10
430
+ max_mock_heavy_tests: 5
431
+ ```
432
+
433
+ Then check compliance:
434
+
435
+ ```bash
436
+ terrain policy check # human-readable output
437
+ terrain policy check --json # JSON output for CI
438
+ ```
439
+
440
+ Exit code 0 = pass, 2 = violations found, 1 = error.
441
+
442
+ ## Documentation
443
+
444
+ - [Quickstart Guide](docs/quickstart.md) — understand your first report in 5 minutes
445
+ - [CLI Specification](docs/cli-spec.md) — full command and flag reference
446
+ - [Example Reports](docs/examples/) — analyze, impact, insights, explain output samples
447
+ - [Canonical User Journeys](docs/product/canonical-user-journeys.md) — primary workflows and expected outcomes
448
+ - [Signal Model](docs/signal-model.md) — the core signal abstraction
449
+ - [Architecture](docs/architecture/) — design documents and technical specifications
450
+ - [Contributing](CONTRIBUTING.md) — how to build, test, and extend Terrain
451
+
452
+ ## Development
453
+
454
+ ```bash
455
+ # Build
456
+ go build -o terrain ./cmd/terrain
457
+
458
+ # Test
459
+ go test ./cmd/... ./internal/...
460
+
461
+ # Full release verification
462
+ make release-verify
463
+ ```
464
+
465
+ ## License
466
+
467
+ Apache License 2.0 — see [LICENSE](LICENSE) for details.
package/SECURITY.md ADDED
@@ -0,0 +1,29 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ Security updates are provided for the latest published major version of Terrain, covering both the Go CLI (`terrain`) and the JavaScript converter package.
6
+
7
+ ## Reporting a Vulnerability
8
+
9
+ If you believe you found a security issue, report it privately:
10
+
11
+ - Open a private GitHub security advisory if available for this repository.
12
+ - If private advisories are unavailable, open a regular issue without exploit details and request a secure contact channel.
13
+
14
+ Please include:
15
+
16
+ - A clear description of the issue and impact
17
+ - Reproduction steps or proof-of-concept input
18
+ - Affected version(s)
19
+ - Any suggested mitigation
20
+
21
+ ## Response Expectations
22
+
23
+ - We aim to acknowledge reports within 3 business days.
24
+ - We aim to provide an initial triage within 7 business days.
25
+ - Confirmed vulnerabilities will be patched and released as quickly as possible.
26
+
27
+ ## Disclosure
28
+
29
+ Please avoid public disclosure until a fix is available and maintainers confirm coordinated disclosure timing.
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { ensureTerrainBinary } from './terrain-installer.js';
4
+
5
+ try {
6
+ await ensureTerrainBinary({ quiet: false });
7
+ } catch (error) {
8
+ process.stderr.write(
9
+ `[mapterrain] Warning: ${error.message}\n` +
10
+ '[mapterrain] The `terrain` command will try again on first run.\n'
11
+ );
12
+ }
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runTerrainCli } from './terrain-installer.js';
4
+
5
+ try {
6
+ await runTerrainCli();
7
+ } catch (error) {
8
+ process.stderr.write(`${error.message}\n`);
9
+ process.exit(1);
10
+ }
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFileSync, spawn } from 'child_process';
4
+ import { createWriteStream, existsSync } from 'fs';
5
+ import fs from 'fs/promises';
6
+ import https from 'https';
7
+ import os from 'os';
8
+ import path from 'path';
9
+ import { pipeline } from 'stream/promises';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ const packageRoot = path.resolve(__dirname, '..');
14
+ const packageJson = JSON.parse(
15
+ await fs.readFile(path.join(packageRoot, 'package.json'), 'utf8')
16
+ );
17
+
18
+ const GITHUB_OWNER = 'pmclSF';
19
+ const GITHUB_REPO = 'terrain';
20
+
21
+ function currentTarget() {
22
+ const goosMap = {
23
+ darwin: 'darwin',
24
+ linux: 'linux',
25
+ win32: 'windows',
26
+ };
27
+ const goarchMap = {
28
+ x64: 'amd64',
29
+ arm64: 'arm64',
30
+ };
31
+
32
+ const goos = goosMap[process.platform];
33
+ const goarch = goarchMap[process.arch];
34
+ if (!goos || !goarch) {
35
+ throw new Error(
36
+ `Unsupported platform ${process.platform}/${process.arch}. ` +
37
+ 'Install Terrain manually from GitHub Releases or via Homebrew.'
38
+ );
39
+ }
40
+
41
+ return {
42
+ goos,
43
+ goarch,
44
+ archiveExt: goos === 'windows' ? 'zip' : 'tar.gz',
45
+ binaryName: goos === 'windows' ? 'terrain.exe' : 'terrain',
46
+ };
47
+ }
48
+
49
+ function isDevelopmentCheckout(rootDir = packageRoot) {
50
+ return existsSync(path.join(rootDir, 'cmd', 'terrain'));
51
+ }
52
+
53
+ function installedBinaryPath(rootDir = packageRoot) {
54
+ const target = currentTarget();
55
+ return path.join(
56
+ rootDir,
57
+ 'vendor',
58
+ 'terrain',
59
+ `${target.goos}-${target.goarch}`,
60
+ target.binaryName
61
+ );
62
+ }
63
+
64
+ function localBuiltBinaryPath(rootDir = packageRoot) {
65
+ const target = currentTarget();
66
+ return path.join(rootDir, target.binaryName);
67
+ }
68
+
69
+ function archiveFileName(version) {
70
+ const target = currentTarget();
71
+ return `terrain_${version}_${target.goos}_${target.goarch}.${target.archiveExt}`;
72
+ }
73
+
74
+ function archiveDownloadUrl(version) {
75
+ const baseUrl =
76
+ process.env.TERRAIN_INSTALLER_BASE_URL ||
77
+ `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases/download`;
78
+ return `${baseUrl}/v${version}/${archiveFileName(version)}`;
79
+ }
80
+
81
+ async function ensureDirectory(dir) {
82
+ await fs.mkdir(dir, { recursive: true });
83
+ }
84
+
85
+ async function copyBinary(sourcePath, destinationPath) {
86
+ await ensureDirectory(path.dirname(destinationPath));
87
+ await fs.copyFile(sourcePath, destinationPath);
88
+ if (process.platform !== 'win32') {
89
+ await fs.chmod(destinationPath, 0o755);
90
+ }
91
+ }
92
+
93
+ function log(message, quiet = false) {
94
+ if (!quiet) {
95
+ process.stderr.write(`${message}\n`);
96
+ }
97
+ }
98
+
99
+ async function downloadFile(url, destinationPath) {
100
+ await new Promise((resolve, reject) => {
101
+ const request = https.get(
102
+ url,
103
+ {
104
+ headers: {
105
+ 'User-Agent': `${packageJson.name}/${packageJson.version}`,
106
+ },
107
+ },
108
+ async (response) => {
109
+ if (
110
+ response.statusCode &&
111
+ response.statusCode >= 300 &&
112
+ response.statusCode < 400 &&
113
+ response.headers.location
114
+ ) {
115
+ response.resume();
116
+ try {
117
+ await downloadFile(response.headers.location, destinationPath);
118
+ resolve();
119
+ } catch (error) {
120
+ reject(error);
121
+ }
122
+ return;
123
+ }
124
+
125
+ if (response.statusCode !== 200) {
126
+ response.resume();
127
+ reject(
128
+ new Error(
129
+ `download failed with HTTP ${response.statusCode} for ${url}`
130
+ )
131
+ );
132
+ return;
133
+ }
134
+
135
+ try {
136
+ await pipeline(response, createWriteStream(destinationPath));
137
+ resolve();
138
+ } catch (error) {
139
+ reject(error);
140
+ }
141
+ }
142
+ );
143
+
144
+ request.on('error', reject);
145
+ });
146
+ }
147
+
148
+ function extractArchive(archivePath, extractDir) {
149
+ if (archivePath.endsWith('.tar.gz')) {
150
+ execFileSync('tar', ['-xzf', archivePath, '-C', extractDir], {
151
+ stdio: 'pipe',
152
+ });
153
+ return;
154
+ }
155
+
156
+ try {
157
+ execFileSync('tar', ['-xf', archivePath, '-C', extractDir], {
158
+ stdio: 'pipe',
159
+ });
160
+ } catch (error) {
161
+ if (process.platform !== 'win32') {
162
+ throw error;
163
+ }
164
+ execFileSync(
165
+ 'powershell.exe',
166
+ [
167
+ '-NoLogo',
168
+ '-NoProfile',
169
+ '-Command',
170
+ `Expand-Archive -LiteralPath '${archivePath}' -DestinationPath '${extractDir}' -Force`,
171
+ ],
172
+ { stdio: 'pipe' }
173
+ );
174
+ }
175
+ }
176
+
177
+ async function findBinary(dir, binaryName) {
178
+ const entries = await fs.readdir(dir, { withFileTypes: true });
179
+ for (const entry of entries) {
180
+ const fullPath = path.join(dir, entry.name);
181
+ if (entry.isFile() && entry.name === binaryName) {
182
+ return fullPath;
183
+ }
184
+ if (entry.isDirectory()) {
185
+ const nested = await findBinary(fullPath, binaryName);
186
+ if (nested) {
187
+ return nested;
188
+ }
189
+ }
190
+ }
191
+ return null;
192
+ }
193
+
194
+ export async function ensureTerrainBinary({
195
+ rootDir = packageRoot,
196
+ quiet = false,
197
+ version = packageJson.version,
198
+ env = process.env,
199
+ } = {}) {
200
+ const binaryPath = installedBinaryPath(rootDir);
201
+ if (existsSync(binaryPath)) {
202
+ return binaryPath;
203
+ }
204
+
205
+ const localOverride = env.TERRAIN_INSTALLER_LOCAL_BINARY;
206
+ if (localOverride && existsSync(localOverride)) {
207
+ log(`Using local Terrain binary override: ${localOverride}`, quiet);
208
+ await copyBinary(localOverride, binaryPath);
209
+ return binaryPath;
210
+ }
211
+
212
+ if (isDevelopmentCheckout(rootDir)) {
213
+ const localBinary = localBuiltBinaryPath(rootDir);
214
+ if (existsSync(localBinary)) {
215
+ return localBinary;
216
+ }
217
+ return null;
218
+ }
219
+
220
+ if (env.TERRAIN_INSTALLER_SKIP_DOWNLOAD === '1') {
221
+ throw new Error(
222
+ 'Terrain binary download skipped because TERRAIN_INSTALLER_SKIP_DOWNLOAD=1.'
223
+ );
224
+ }
225
+
226
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'terrain-install-'));
227
+ const archivePath = path.join(tempDir, archiveFileName(version));
228
+ const extractDir = path.join(tempDir, 'extract');
229
+
230
+ try {
231
+ log(
232
+ `Downloading Terrain ${version} for ${process.platform}/${process.arch}...`,
233
+ quiet
234
+ );
235
+ await downloadFile(archiveDownloadUrl(version), archivePath);
236
+ await ensureDirectory(extractDir);
237
+ extractArchive(archivePath, extractDir);
238
+
239
+ const extractedBinary = await findBinary(
240
+ extractDir,
241
+ currentTarget().binaryName
242
+ );
243
+ if (!extractedBinary) {
244
+ throw new Error(
245
+ `downloaded archive ${path.basename(archivePath)} did not contain ${currentTarget().binaryName}`
246
+ );
247
+ }
248
+
249
+ await copyBinary(extractedBinary, binaryPath);
250
+ log(`Installed Terrain binary to ${binaryPath}`, quiet);
251
+ return binaryPath;
252
+ } finally {
253
+ await fs.rm(tempDir, { recursive: true, force: true });
254
+ }
255
+ }
256
+
257
+ export async function runTerrainCli(argv = process.argv.slice(2)) {
258
+ const rootDir = packageRoot;
259
+
260
+ if (isDevelopmentCheckout(rootDir)) {
261
+ const localBinary = localBuiltBinaryPath(rootDir);
262
+ if (existsSync(localBinary)) {
263
+ await runBinary(localBinary, argv);
264
+ return;
265
+ }
266
+
267
+ await runBinary('go', ['run', './cmd/terrain', ...argv], rootDir);
268
+ return;
269
+ }
270
+
271
+ let binaryPath;
272
+ try {
273
+ binaryPath = await ensureTerrainBinary({ rootDir });
274
+ } catch (error) {
275
+ throw new Error(
276
+ `${error.message}\n\n` +
277
+ 'Fallback install options:\n' +
278
+ ' brew install pmclSF/terrain/mapterrain\n' +
279
+ ' go install github.com/pmclSF/terrain/cmd/terrain@latest'
280
+ );
281
+ }
282
+
283
+ if (!binaryPath) {
284
+ throw new Error(
285
+ 'No Terrain binary is available in this checkout yet. ' +
286
+ 'Build it with `go build -o terrain ./cmd/terrain` or run the Go CLI directly.'
287
+ );
288
+ }
289
+
290
+ await runBinary(binaryPath, argv);
291
+ }
292
+
293
+ async function runBinary(command, args, cwd = undefined) {
294
+ await new Promise((resolve, reject) => {
295
+ const child = spawn(command, args, {
296
+ cwd,
297
+ stdio: 'inherit',
298
+ });
299
+
300
+ child.on('error', reject);
301
+ child.on('exit', (code, signal) => {
302
+ if (signal) {
303
+ process.kill(process.pid, signal);
304
+ return;
305
+ }
306
+ process.exitCode = code ?? 1;
307
+ resolve();
308
+ });
309
+ });
310
+ }
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "mapterrain",
3
+ "version": "0.1.0",
4
+ "description": "Terrain test intelligence CLI.",
5
+ "type": "module",
6
+ "bin": {
7
+ "mapterrain": "bin/terrain-cli.js",
8
+ "terrain": "bin/terrain-cli.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "README.md",
13
+ "SECURITY.md",
14
+ "LICENSE"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/pmclSF/terrain.git"
19
+ },
20
+ "homepage": "https://github.com/pmclSF/terrain#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/pmclSF/terrain/issues"
23
+ },
24
+ "scripts": {
25
+ "postinstall": "node bin/postinstall.js",
26
+ "test": "node scripts/verify-pack.js",
27
+ "lint": "eslint \"bin/*.js\" \"scripts/*.js\"",
28
+ "format": "prettier --write \"bin/*.js\" \"scripts/*.js\"",
29
+ "format:check": "prettier --check \"bin/*.js\" \"scripts/*.js\"",
30
+ "start": "node bin/terrain-cli.js",
31
+ "convert": "node bin/terrain-cli.js convert",
32
+ "lint-staged": "lint-staged",
33
+ "release:verify": "npm run format:check && npm run lint && npm test",
34
+ "prepublishOnly": "npm run format:check && npm run lint && npm test"
35
+ },
36
+ "keywords": [
37
+ "terrain",
38
+ "test-intelligence",
39
+ "test-analysis",
40
+ "risk-analysis",
41
+ "playwright",
42
+ "cypress",
43
+ "selenium",
44
+ "jest",
45
+ "vitest",
46
+ "mocha",
47
+ "jasmine",
48
+ "junit",
49
+ "testng",
50
+ "pytest",
51
+ "unittest",
52
+ "nose2",
53
+ "webdriverio",
54
+ "puppeteer",
55
+ "testcafe",
56
+ "testing",
57
+ "automation",
58
+ "migration",
59
+ "e2e-testing",
60
+ "test-migration",
61
+ "test-converter"
62
+ ],
63
+ "author": "pmclSF",
64
+ "license": "Apache-2.0",
65
+ "dependencies": {},
66
+ "devDependencies": {
67
+ "@commitlint/cli": "^19.6.1",
68
+ "@commitlint/config-conventional": "^19.6.0",
69
+ "eslint": "^8.57.1",
70
+ "eslint-config-prettier": "^10.1.8",
71
+ "lint-staged": "^16.2.7",
72
+ "prettier": "^3.0.0"
73
+ },
74
+ "lint-staged": {
75
+ "{bin,scripts}/*.js": [
76
+ "prettier --write",
77
+ "eslint --fix --max-warnings=0"
78
+ ]
79
+ },
80
+ "engines": {
81
+ "node": ">=22.0.0"
82
+ },
83
+ "sideEffects": false
84
+ }