@deployanyway/ship-it-meter 0.3.0 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ Twelve named demonstration scenarios show passing evidence, Friday releases, missing evidence, failed builds, failing tests, low coverage, critical issues, lint failures, dirty worktrees, feature branches, zero tests and weekend incidents. These are examples, never evidence about your real project.
6
+
7
+ ```sh
8
+
9
+
3
10
  ## 0.3.0 — 2026-10-08
4
11
 
5
12
  - Useful structured API/CLI additions described in README.
@@ -21,3 +28,4 @@
21
28
 
22
29
  - Deterministic readiness score with explanations and technical blocker caps.
23
30
  - CLI, documented scoring rules, tests, linting, formatting, and Node 22/24 CI.
31
+ ```
package/MIGRATION.md CHANGED
@@ -3,3 +3,9 @@
3
3
  Node 22.13+ is required. Existing core APIs remain available; TypeScript declarations and CommonJS exports are new. The CLI now lives in src/cli.js behind the same executable path. Input/stdout behavior for new options is documented in README.
4
4
 
5
5
  Install 0.3.0 with npm. Seeds and exact humorous wording are version-specific. Do not treat jokes or heuristic scores as production evidence.
6
+
7
+ ## 0.3.0 to 0.4.0
8
+
9
+ 12 discoverable evidence scenarios and releasePlan with ordered blocker/review/release tasks and observable completion criteria; API and CLI scenarios/plans; passing/blocked gate exit codes preserved.
10
+
11
+ Existing defaults and entry points remain available. The new release plan and demonstration scenarios are opt-in; the original scoring and gate rules are retained.
package/README.md CHANGED
@@ -1,6 +1,29 @@
1
1
  # ship-it-meter
2
2
 
3
- > **Version 0.3.0:** install from npm with Node 22.13+ or Node 24. See MIGRATION.md for changes from 0.2.0.
3
+ ## Evidence into an actual release plan (0.4.0)
4
+
5
+ Twelve named demonstration scenarios show passing evidence, Friday releases, missing evidence, failed builds, failing tests, low coverage, critical issues, lint failures, dirty worktrees, feature branches, zero tests and weekend incidents. These are examples, never evidence about your real project.
6
+
7
+ ```sh
8
+ npx @deployanyway/ship-it-meter --list-scenarios
9
+ npx @deployanyway/ship-it-meter --scenario failed-build --plan
10
+ npx @deployanyway/ship-it-meter --scenario ready --plan --json
11
+ ```
12
+
13
+ ```js
14
+ import {
15
+ releasePlan,
16
+ releaseScenarios,
17
+ scenarioEvidence,
18
+ } from "@deployanyway/ship-it-meter";
19
+ console.log(releaseScenarios());
20
+ const plan = releasePlan(scenarioEvidence("failing-tests"), { minScore: 80 });
21
+ console.log(plan.passed, plan.summary, plan.tasks);
22
+ ```
23
+
24
+ `releasePlan(evidence?, options?)` includes the existing gate plus prioritized tasks with stable IDs and observable verification criteria. Priorities are blocker, review and release. `scenarioEvidence(name)` and `releaseScenarios()` return independent evidence copies. CLI flags override scenario fields; stdin and scenario cannot be combined. Choose one of plan, gate or checklist. A blocked plan/gate exits 1; invalid input exits 2. Readiness remains a heuristic based on caller-supplied evidence; it does not run CI or approve deployment automatically. Coverage below 80 adds a review task, not a separate hard blocker; the configured score threshold and existing gate rules still decide the gate.
25
+
26
+ > **Version 0.4.0:** install from npm with Node 22.13+ or Node 24. See MIGRATION.md for changes from 0.2.0.
4
27
 
5
28
  [![npm version](https://img.shields.io/npm/v/%40deployanyway%2Fship-it-meter)](https://www.npmjs.com/package/@deployanyway/ship-it-meter)
6
29
  [![CI](https://github.com/DeployAnyway/ship-it-meter/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/DeployAnyway/ship-it-meter/actions/workflows/ci.yml)
package/dist/index.cjs CHANGED
@@ -22,9 +22,136 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  preflight: () => preflight,
24
24
  releaseGate: () => releaseGate,
25
+ releasePlan: () => releasePlan,
26
+ releaseScenarios: () => releaseScenarios,
27
+ scenarioEvidence: () => scenarioEvidence,
25
28
  shipIt: () => shipIt
26
29
  });
27
30
  module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/scenarios.js
33
+ var scenarios = {
34
+ ready: {
35
+ description: "Passing evidence, clean worktree and main branch. Boring is a compliment.",
36
+ evidence: {
37
+ tests: 125,
38
+ coverage: 96,
39
+ build: true,
40
+ branch: "main",
41
+ day: "tuesday"
42
+ }
43
+ },
44
+ friday: {
45
+ description: "Good evidence on Friday. Confirm rollback and on-call before the victory lap.",
46
+ evidence: {
47
+ tests: 125,
48
+ coverage: 92,
49
+ build: true,
50
+ branch: "main",
51
+ day: "friday"
52
+ }
53
+ },
54
+ "no-evidence": {
55
+ description: "Confidence is present. Test, coverage and build evidence have not arrived.",
56
+ evidence: {}
57
+ },
58
+ "failed-build": {
59
+ description: "The build is red. The deploy button is not a color correction tool.",
60
+ evidence: {
61
+ tests: 125,
62
+ coverage: 92,
63
+ build: false
64
+ }
65
+ },
66
+ "failing-tests": {
67
+ description: "Some tests fail. Green deployment lights will not repair assertions.",
68
+ evidence: {
69
+ tests: 125,
70
+ failingTests: 3,
71
+ coverage: 92,
72
+ build: true
73
+ }
74
+ },
75
+ "low-coverage": {
76
+ description: "A passing build with many untested paths. The bugs appreciate the privacy.",
77
+ evidence: {
78
+ tests: 125,
79
+ coverage: 45,
80
+ build: true
81
+ }
82
+ },
83
+ "critical-issue": {
84
+ description: "A critical issue blocks the gate even when everything else looks cheerful.",
85
+ evidence: {
86
+ tests: 125,
87
+ coverage: 96,
88
+ build: true,
89
+ criticalIssues: 1
90
+ }
91
+ },
92
+ "lint-failures": {
93
+ description: "Lint failures are still failures, however decorative the semicolons feel.",
94
+ evidence: {
95
+ tests: 125,
96
+ coverage: 96,
97
+ build: true,
98
+ lintFailures: 2
99
+ }
100
+ },
101
+ "dirty-worktree": {
102
+ description: "The laptop contains uncommitted changes. It is not the release archive.",
103
+ evidence: {
104
+ tests: 125,
105
+ coverage: 96,
106
+ build: true,
107
+ uncommittedChanges: true
108
+ }
109
+ },
110
+ "feature-branch": {
111
+ description: "Healthy evidence on a feature branch. Verify the intended release source.",
112
+ evidence: {
113
+ tests: 125,
114
+ coverage: 96,
115
+ build: true,
116
+ branch: "feature/zoomies"
117
+ }
118
+ },
119
+ "zero-tests": {
120
+ description: "No tests ran. Zero failures is an especially small achievement here.",
121
+ evidence: {
122
+ tests: 0,
123
+ coverage: 0,
124
+ build: true
125
+ }
126
+ },
127
+ "weekend-incident": {
128
+ description: "A failed build, failing tests and a critical issue. Bring evidence, not a bigger confidence hat.",
129
+ evidence: {
130
+ tests: 125,
131
+ failingTests: 12,
132
+ coverage: 60,
133
+ build: false,
134
+ criticalIssues: 1,
135
+ day: "saturday"
136
+ }
137
+ }
138
+ };
139
+ function releaseScenarios() {
140
+ return Object.entries(scenarios).map(([name, value]) => ({
141
+ name,
142
+ description: value.description,
143
+ evidence: { ...value.evidence }
144
+ }));
145
+ }
146
+ function scenarioEvidence(name) {
147
+ if (typeof name !== "string" || !Object.hasOwn(scenarios, name))
148
+ throw new RangeError(
149
+ "Unknown scenario. Choose: " + Object.keys(scenarios).join(", ")
150
+ );
151
+ return { ...scenarios[name].evidence };
152
+ }
153
+
154
+ // src/index.js
28
155
  var days = [
29
156
  "monday",
30
157
  "tuesday",
@@ -175,9 +302,116 @@ function preflight(input = {}) {
175
302
  );
176
303
  return { ...result, actions };
177
304
  }
305
+ function releasePlan(input = {}, options = {}) {
306
+ const gate = releaseGate(input, options);
307
+ const tasks = [];
308
+ const add = (id, priority, title, verify) => tasks.push({ id, priority, title, verify });
309
+ if (input.build !== true)
310
+ add(
311
+ "build",
312
+ "blocker",
313
+ "Get a passing build",
314
+ "Run the release build for the exact commit and record its successful result."
315
+ );
316
+ if (!input.tests)
317
+ add(
318
+ "tests",
319
+ "blocker",
320
+ "Run a nonempty test suite",
321
+ "Record the test count and outcome for the release commit."
322
+ );
323
+ if (input.failingTests)
324
+ add(
325
+ "failing-tests",
326
+ "blocker",
327
+ "Repair failing tests",
328
+ "Reproduce each failure, fix its cause and rerun the suite."
329
+ );
330
+ if (input.coverage === void 0)
331
+ add(
332
+ "coverage-evidence",
333
+ "blocker",
334
+ "Measure coverage",
335
+ "Generate coverage for the release commit and record the percentage."
336
+ );
337
+ else if (input.coverage < 80)
338
+ add(
339
+ "coverage-review",
340
+ "review",
341
+ "Review untested paths",
342
+ "Inspect uncovered critical paths and add meaningful tests where needed."
343
+ );
344
+ if (input.criticalIssues)
345
+ add(
346
+ "critical-issues",
347
+ "blocker",
348
+ "Resolve critical issues",
349
+ "Record the resolution and verify the affected behavior."
350
+ );
351
+ if (input.lintFailures)
352
+ add(
353
+ "lint",
354
+ "blocker",
355
+ "Fix lint failures",
356
+ "Run the configured lint checks successfully."
357
+ );
358
+ if (input.uncommittedChanges)
359
+ add(
360
+ "worktree",
361
+ "blocker",
362
+ "Reconcile local changes",
363
+ "Commit or stash intentionally and verify the exact release source."
364
+ );
365
+ if (input.branch && !["main", "master"].includes(input.branch.trim()))
366
+ add(
367
+ "branch",
368
+ "review",
369
+ "Confirm release branch",
370
+ "Verify that this branch is the intended release source and that its CI passed."
371
+ );
372
+ if (input.day?.trim().toLowerCase() === "friday")
373
+ add(
374
+ "friday",
375
+ "review",
376
+ "Confirm weekend support",
377
+ "Identify monitoring ownership and a tested rollback path."
378
+ );
379
+ if (gate.score < gate.minScore)
380
+ add(
381
+ "score",
382
+ "blocker",
383
+ "Improve evidence to the configured threshold",
384
+ "Address score deductions, rerun the checks and reevaluate the gate."
385
+ );
386
+ add(
387
+ "rollback",
388
+ "release",
389
+ "Prepare the return route",
390
+ "Verify the rollback artifact and procedure before deployment."
391
+ );
392
+ add(
393
+ "monitoring",
394
+ "release",
395
+ "Watch the actual release",
396
+ "Check health and error metrics after deployment and name an owner."
397
+ );
398
+ const summaries = {
399
+ "Absolutely Not": "The evidence says stop. The confidence hat can wait.",
400
+ Questionable: "The release has questions. Answer them before adding confetti.",
401
+ "Probably Fine": "Close is a useful direction, not a deployment receipt.",
402
+ "Ship It": "Good evidence. Keep the parachute and watch the landing.",
403
+ "Suspiciously Ready": "Boring, verified readiness. Give the checks their share of the applause."
404
+ };
405
+ const rank = { blocker: 0, review: 1, release: 2 };
406
+ tasks.sort((a, b) => rank[a.priority] - rank[b.priority]);
407
+ return { ...gate, summary: summaries[gate.verdict], tasks };
408
+ }
178
409
  // Annotate the CommonJS export names for ESM import in node:
179
410
  0 && (module.exports = {
180
411
  preflight,
181
412
  releaseGate,
413
+ releasePlan,
414
+ releaseScenarios,
415
+ scenarioEvidence,
182
416
  shipIt
183
417
  });
package/index.d.cts CHANGED
@@ -41,3 +41,25 @@ export function releaseGate(
41
41
  input?: Evidence,
42
42
  options?: { minScore?: number },
43
43
  ): Gate;
44
+
45
+ export interface ReleaseTask {
46
+ id: string;
47
+ priority: "blocker" | "review" | "release";
48
+ title: string;
49
+ verify: string;
50
+ }
51
+ export interface ReleasePlan extends Gate {
52
+ summary: string;
53
+ tasks: ReleaseTask[];
54
+ }
55
+ export interface ReleaseScenario {
56
+ name: string;
57
+ description: string;
58
+ evidence: Evidence;
59
+ }
60
+ export function releaseScenarios(): ReleaseScenario[];
61
+ export function scenarioEvidence(name: string): Evidence;
62
+ export function releasePlan(
63
+ input?: Evidence,
64
+ options?: { minScore?: number },
65
+ ): ReleasePlan;
package/index.d.ts CHANGED
@@ -41,3 +41,25 @@ export function releaseGate(
41
41
  input?: Evidence,
42
42
  options?: { minScore?: number },
43
43
  ): Gate;
44
+
45
+ export interface ReleaseTask {
46
+ id: string;
47
+ priority: "blocker" | "review" | "release";
48
+ title: string;
49
+ verify: string;
50
+ }
51
+ export interface ReleasePlan extends Gate {
52
+ summary: string;
53
+ tasks: ReleaseTask[];
54
+ }
55
+ export interface ReleaseScenario {
56
+ name: string;
57
+ description: string;
58
+ evidence: Evidence;
59
+ }
60
+ export function releaseScenarios(): ReleaseScenario[];
61
+ export function scenarioEvidence(name: string): Evidence;
62
+ export function releasePlan(
63
+ input?: Evidence,
64
+ options?: { minScore?: number },
65
+ ): ReleasePlan;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deployanyway/ship-it-meter",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Explainable deployment readiness scoring for tests, coverage, and builds. Turns questionable confidence into a number.",
5
5
  "type": "module",
6
6
  "exports": {
package/src/cli.js CHANGED
@@ -1,7 +1,14 @@
1
1
  import { parseArgs } from "node:util";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { URL } from "node:url";
4
- import { shipIt, preflight, releaseGate } from "./index.js";
4
+ import {
5
+ shipIt,
6
+ preflight,
7
+ releaseGate,
8
+ releasePlan,
9
+ releaseScenarios,
10
+ scenarioEvidence,
11
+ } from "./index.js";
5
12
 
6
13
  import { readStdin } from "./input.js";
7
14
 
@@ -13,6 +20,9 @@ try {
13
20
  json: { type: "boolean" },
14
21
  stdin: { type: "boolean" },
15
22
  gate: { type: "boolean" },
23
+ plan: { type: "boolean" },
24
+ scenario: { type: "string" },
25
+ "list-scenarios": { type: "boolean" },
16
26
  "min-score": { type: "string" },
17
27
  checklist: { type: "boolean" },
18
28
  tests: { type: "string" },
@@ -28,7 +38,7 @@ try {
28
38
  });
29
39
  if (values.help) {
30
40
  console.log(
31
- "Usage: ship-it-meter [options]\n\nOptions:\n --tests number Total tests\n --failing number Failing tests\n --coverage number Coverage percent\n --build pass|fail Build result\n --critical number Open critical issues\n --lint number Lint failures\n --dirty Uncommitted changes\n --branch name Branch name\n --day weekday Full weekday name\n --checklist Actionable, mildly concerned preflight\n --json Structured output\n -h, --help Help\n -v, --version Version\n\n--stdin reads a JSON evidence object (256 KiB); explicit flags override it. --gate requires passing evidence; --min-score 80 selects its threshold. A blocked gate exits 1. Missing test, coverage, and build evidence reduces readiness.\nExit codes: 0 scored successfully; 2 invalid arguments.",
41
+ "Usage: ship-it-meter [options]\n\nOptions:\n --tests number Total tests\n --failing number Failing tests\n --coverage number Coverage percent\n --build pass|fail Build result\n --critical number Open critical issues\n --lint number Lint failures\n --dirty Uncommitted changes\n --branch name Branch name\n --day weekday Full weekday name\n --scenario name Explore a named evidence scenario\n --list-scenarios List sample scenarios\n --plan Gate plus prioritized tasks and verification criteria\n --checklist Actionable, mildly concerned preflight\n --json Structured output\n -h, --help Help\n -v, --version Version\n\n--stdin reads a JSON evidence object (256 KiB); explicit flags override it. --gate requires passing evidence; --min-score 80 selects its threshold. A blocked gate exits 1. Missing test, coverage, and build evidence reduces readiness.\nExit codes: 0 scored successfully; 2 invalid arguments.",
32
42
  );
33
43
  } else if (values.version) {
34
44
  console.log(
@@ -36,15 +46,32 @@ try {
36
46
  readFileSync(new URL("../package.json", import.meta.url), "utf8"),
37
47
  ).version,
38
48
  );
49
+ } else if (values["list-scenarios"]) {
50
+ console.log(
51
+ releaseScenarios()
52
+ .map((item) => item.name + " — " + item.description)
53
+ .join("\n"),
54
+ );
39
55
  } else {
40
- const input = values.stdin ? JSON.parse(await readStdin()) : {};
56
+ const input = values.stdin
57
+ ? JSON.parse(await readStdin())
58
+ : values.scenario !== undefined
59
+ ? scenarioEvidence(values.scenario)
60
+ : {};
61
+ if (values.stdin && values.scenario !== undefined)
62
+ throw new TypeError("Choose --stdin or --scenario, not both.");
63
+ if ([values.gate, values.plan, values.checklist].filter(Boolean).length > 1)
64
+ throw new TypeError("Choose one of --gate, --plan or --checklist.");
41
65
  if (!input || typeof input !== "object" || Array.isArray(input))
42
66
  throw new TypeError("stdin must contain an evidence object.");
43
67
  if (
44
68
  values["min-score"] !== undefined &&
45
- (!values.gate || !/^\d+(?:\.\d+)?$/.test(values["min-score"]))
69
+ (!(values.gate || values.plan) ||
70
+ !/^\d+(?:\.\d+)?$/.test(values["min-score"]))
46
71
  )
47
- throw new TypeError("--min-score requires --gate and a decimal number.");
72
+ throw new TypeError(
73
+ "--min-score requires --gate or --plan and a decimal number.",
74
+ );
48
75
  for (const [flag, key] of Object.entries({
49
76
  tests: "tests",
50
77
  failing: "failingTests",
@@ -66,21 +93,34 @@ try {
66
93
  for (const key of ["day", "branch"])
67
94
  if (values[key] !== undefined) input[key] = values[key];
68
95
  if (values.dirty) input.uncommittedChanges = true;
69
- const result = values.gate
70
- ? releaseGate(input, {
71
- minScore:
72
- values["min-score"] === undefined
73
- ? undefined
74
- : Number(values["min-score"]),
75
- })
76
- : values.checklist
77
- ? preflight(input)
78
- : shipIt(input);
79
- if (values.gate && !result.passed) process.exitCode = 1;
96
+ const result =
97
+ values.gate || values.plan
98
+ ? (values.plan ? releasePlan : releaseGate)(input, {
99
+ minScore:
100
+ values["min-score"] === undefined
101
+ ? undefined
102
+ : Number(values["min-score"]),
103
+ })
104
+ : values.checklist
105
+ ? preflight(input)
106
+ : shipIt(input);
107
+ if ((values.gate || values.plan) && !result.passed) process.exitCode = 1;
108
+ if (values.plan && !values.json)
109
+ console.log(
110
+ result.summary +
111
+ "\n" +
112
+ result.tasks
113
+ .map(
114
+ (task) =>
115
+ `[${task.priority}] ${task.title}\n Verify: ${task.verify}`,
116
+ )
117
+ .join("\n") +
118
+ "\n",
119
+ );
80
120
  console.log(
81
121
  values.json
82
122
  ? JSON.stringify(result, null, 2)
83
- : `${result.score}/100 — ${result.verdict}${values.gate ? (result.passed ? " — gate passed" : " — gate blocked") : ""}\n${result.reasons.map((reason) => `- ${reason}`).join("\n")}${result.actions ? "\n\nBefore you ship:\n" + result.actions.map((action) => `[ ] ${action}`).join("\n") : ""}`,
123
+ : `${result.score}/100 — ${result.verdict}${values.gate || values.plan ? (result.passed ? " — gate passed" : " — gate blocked") : ""}\n${result.reasons.map((reason) => `- ${reason}`).join("\n")}${result.actions ? "\n\nBefore you ship:\n" + result.actions.map((action) => `[ ] ${action}`).join("\n") : ""}`,
84
124
  );
85
125
  }
86
126
  } catch (error) {
package/src/index.js CHANGED
@@ -187,3 +187,113 @@ export function preflight(input = {}) {
187
187
  );
188
188
  return { ...result, actions };
189
189
  }
190
+
191
+ export { releaseScenarios, scenarioEvidence } from "./scenarios.js";
192
+ /** Explain the gate as ordered work with observable completion criteria. */
193
+ export function releasePlan(input = {}, options = {}) {
194
+ const gate = releaseGate(input, options);
195
+ const tasks = [];
196
+ const add = (id, priority, title, verify) =>
197
+ tasks.push({ id, priority, title, verify });
198
+ if (input.build !== true)
199
+ add(
200
+ "build",
201
+ "blocker",
202
+ "Get a passing build",
203
+ "Run the release build for the exact commit and record its successful result.",
204
+ );
205
+ if (!input.tests)
206
+ add(
207
+ "tests",
208
+ "blocker",
209
+ "Run a nonempty test suite",
210
+ "Record the test count and outcome for the release commit.",
211
+ );
212
+ if (input.failingTests)
213
+ add(
214
+ "failing-tests",
215
+ "blocker",
216
+ "Repair failing tests",
217
+ "Reproduce each failure, fix its cause and rerun the suite.",
218
+ );
219
+ if (input.coverage === undefined)
220
+ add(
221
+ "coverage-evidence",
222
+ "blocker",
223
+ "Measure coverage",
224
+ "Generate coverage for the release commit and record the percentage.",
225
+ );
226
+ else if (input.coverage < 80)
227
+ add(
228
+ "coverage-review",
229
+ "review",
230
+ "Review untested paths",
231
+ "Inspect uncovered critical paths and add meaningful tests where needed.",
232
+ );
233
+ if (input.criticalIssues)
234
+ add(
235
+ "critical-issues",
236
+ "blocker",
237
+ "Resolve critical issues",
238
+ "Record the resolution and verify the affected behavior.",
239
+ );
240
+ if (input.lintFailures)
241
+ add(
242
+ "lint",
243
+ "blocker",
244
+ "Fix lint failures",
245
+ "Run the configured lint checks successfully.",
246
+ );
247
+ if (input.uncommittedChanges)
248
+ add(
249
+ "worktree",
250
+ "blocker",
251
+ "Reconcile local changes",
252
+ "Commit or stash intentionally and verify the exact release source.",
253
+ );
254
+ if (input.branch && !["main", "master"].includes(input.branch.trim()))
255
+ add(
256
+ "branch",
257
+ "review",
258
+ "Confirm release branch",
259
+ "Verify that this branch is the intended release source and that its CI passed.",
260
+ );
261
+ if (input.day?.trim().toLowerCase() === "friday")
262
+ add(
263
+ "friday",
264
+ "review",
265
+ "Confirm weekend support",
266
+ "Identify monitoring ownership and a tested rollback path.",
267
+ );
268
+ if (gate.score < gate.minScore)
269
+ add(
270
+ "score",
271
+ "blocker",
272
+ "Improve evidence to the configured threshold",
273
+ "Address score deductions, rerun the checks and reevaluate the gate.",
274
+ );
275
+ add(
276
+ "rollback",
277
+ "release",
278
+ "Prepare the return route",
279
+ "Verify the rollback artifact and procedure before deployment.",
280
+ );
281
+ add(
282
+ "monitoring",
283
+ "release",
284
+ "Watch the actual release",
285
+ "Check health and error metrics after deployment and name an owner.",
286
+ );
287
+ const summaries = {
288
+ "Absolutely Not": "The evidence says stop. The confidence hat can wait.",
289
+ Questionable:
290
+ "The release has questions. Answer them before adding confetti.",
291
+ "Probably Fine": "Close is a useful direction, not a deployment receipt.",
292
+ "Ship It": "Good evidence. Keep the parachute and watch the landing.",
293
+ "Suspiciously Ready":
294
+ "Boring, verified readiness. Give the checks their share of the applause.",
295
+ };
296
+ const rank = { blocker: 0, review: 1, release: 2 };
297
+ tasks.sort((a, b) => rank[a.priority] - rank[b.priority]);
298
+ return { ...gate, summary: summaries[gate.verdict], tasks };
299
+ }
@@ -0,0 +1,133 @@
1
+ // Demonstration inputs, never a claim about a real release.
2
+ const scenarios = {
3
+ ready: {
4
+ description:
5
+ "Passing evidence, clean worktree and main branch. Boring is a compliment.",
6
+ evidence: {
7
+ tests: 125,
8
+ coverage: 96,
9
+ build: true,
10
+ branch: "main",
11
+ day: "tuesday",
12
+ },
13
+ },
14
+ friday: {
15
+ description:
16
+ "Good evidence on Friday. Confirm rollback and on-call before the victory lap.",
17
+ evidence: {
18
+ tests: 125,
19
+ coverage: 92,
20
+ build: true,
21
+ branch: "main",
22
+ day: "friday",
23
+ },
24
+ },
25
+ "no-evidence": {
26
+ description:
27
+ "Confidence is present. Test, coverage and build evidence have not arrived.",
28
+ evidence: {},
29
+ },
30
+ "failed-build": {
31
+ description:
32
+ "The build is red. The deploy button is not a color correction tool.",
33
+ evidence: {
34
+ tests: 125,
35
+ coverage: 92,
36
+ build: false,
37
+ },
38
+ },
39
+ "failing-tests": {
40
+ description:
41
+ "Some tests fail. Green deployment lights will not repair assertions.",
42
+ evidence: {
43
+ tests: 125,
44
+ failingTests: 3,
45
+ coverage: 92,
46
+ build: true,
47
+ },
48
+ },
49
+ "low-coverage": {
50
+ description:
51
+ "A passing build with many untested paths. The bugs appreciate the privacy.",
52
+ evidence: {
53
+ tests: 125,
54
+ coverage: 45,
55
+ build: true,
56
+ },
57
+ },
58
+ "critical-issue": {
59
+ description:
60
+ "A critical issue blocks the gate even when everything else looks cheerful.",
61
+ evidence: {
62
+ tests: 125,
63
+ coverage: 96,
64
+ build: true,
65
+ criticalIssues: 1,
66
+ },
67
+ },
68
+ "lint-failures": {
69
+ description:
70
+ "Lint failures are still failures, however decorative the semicolons feel.",
71
+ evidence: {
72
+ tests: 125,
73
+ coverage: 96,
74
+ build: true,
75
+ lintFailures: 2,
76
+ },
77
+ },
78
+ "dirty-worktree": {
79
+ description:
80
+ "The laptop contains uncommitted changes. It is not the release archive.",
81
+ evidence: {
82
+ tests: 125,
83
+ coverage: 96,
84
+ build: true,
85
+ uncommittedChanges: true,
86
+ },
87
+ },
88
+ "feature-branch": {
89
+ description:
90
+ "Healthy evidence on a feature branch. Verify the intended release source.",
91
+ evidence: {
92
+ tests: 125,
93
+ coverage: 96,
94
+ build: true,
95
+ branch: "feature/zoomies",
96
+ },
97
+ },
98
+ "zero-tests": {
99
+ description:
100
+ "No tests ran. Zero failures is an especially small achievement here.",
101
+ evidence: {
102
+ tests: 0,
103
+ coverage: 0,
104
+ build: true,
105
+ },
106
+ },
107
+ "weekend-incident": {
108
+ description:
109
+ "A failed build, failing tests and a critical issue. Bring evidence, not a bigger confidence hat.",
110
+ evidence: {
111
+ tests: 125,
112
+ failingTests: 12,
113
+ coverage: 60,
114
+ build: false,
115
+ criticalIssues: 1,
116
+ day: "saturday",
117
+ },
118
+ },
119
+ };
120
+ export function releaseScenarios() {
121
+ return Object.entries(scenarios).map(([name, value]) => ({
122
+ name,
123
+ description: value.description,
124
+ evidence: { ...value.evidence },
125
+ }));
126
+ }
127
+ export function scenarioEvidence(name) {
128
+ if (typeof name !== "string" || !Object.hasOwn(scenarios, name))
129
+ throw new RangeError(
130
+ "Unknown scenario. Choose: " + Object.keys(scenarios).join(", "),
131
+ );
132
+ return { ...scenarios[name].evidence };
133
+ }