@mmnto/cli 2.0.0 → 2.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.
@@ -0,0 +1,226 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
7
+ // Executed regression cases for tools/release-train-shape.sh — the legs-gate CI
8
+ // arm's one exemption (mmnto-ai/totem#2779). The workflow pins the script's
9
+ // invocation (tools-hook-parity.test.ts); this file runs the script against a
10
+ // throwaway git repository and reads what it writes to $GITHUB_OUTPUT, so the
11
+ // rule "exempt only when the diff is exactly the release train's work" is
12
+ // proven by execution, case by case, including the bypass Greptile named on
13
+ // mmnto-ai/totem#2780: deleting a pending changeset without consuming them all.
14
+ const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../..');
15
+ const SCRIPT = path.join(REPO_ROOT, 'tools', 'release-train-shape.sh');
16
+ // A changeset body long enough that git's rename detection would pair its
17
+ // deletion with an ADDED file of the same text (the first-release case below
18
+ // reuses it verbatim): the script disables rename detection, so the pair must
19
+ // still read as D + A and stay exempt (the leg's F3 on mmnto-ai/totem#2780).
20
+ const CHANGESET_A = [
21
+ '---',
22
+ '"@example/y": minor',
23
+ '---',
24
+ '',
25
+ 'Add the first feature of package y, with enough prose that a rename',
26
+ 'heuristic would call the deleted changeset and the new CHANGELOG the same file.',
27
+ '',
28
+ ].join(String.fromCharCode(10));
29
+ // A POSIX bash that is not WSL (a WSL bash cannot run against a Windows temp
30
+ // path; the ubuntu and macos legs of the matrix run these cases regardless).
31
+ function bashUsable() {
32
+ const probe = spawnSync('bash', ['-c', 'uname -r'], { encoding: 'utf-8', timeout: 5000 });
33
+ if (probe.status !== 0)
34
+ return false;
35
+ return !/microsoft/i.test(probe.stdout);
36
+ }
37
+ const BASH_OK = bashUsable();
38
+ function git(cwd, ...args) {
39
+ const r = spawnSync('git', args, { cwd, encoding: 'utf-8' });
40
+ if (r.status !== 0)
41
+ throw new Error(`git ${args.join(' ')} failed: ${r.stderr}`);
42
+ return r.stdout;
43
+ }
44
+ function write(cwd, rel, content) {
45
+ const abs = path.join(cwd, rel);
46
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
47
+ fs.writeFileSync(abs, content, 'utf-8');
48
+ }
49
+ /**
50
+ * Run the script with HEAD at `cwd`'s checkout against `base`; read
51
+ * $GITHUB_OUTPUT. The output file lives OUTSIDE the fixture repository, or the
52
+ * next scenario's `git add -A` would commit it and read it as a changed path.
53
+ */
54
+ function shape(cwd, base) {
55
+ const outFile = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'totem-gh-output-')), 'out');
56
+ fs.writeFileSync(outFile, '', 'utf-8');
57
+ const r = spawnSync('bash', [SCRIPT, base], {
58
+ cwd,
59
+ encoding: 'utf-8',
60
+ env: { ...process.env, GITHUB_OUTPUT: outFile },
61
+ });
62
+ return { status: r.status, exempt: fs.readFileSync(outFile, 'utf-8').trim(), log: r.stdout };
63
+ }
64
+ describe.skipIf(!BASH_OK)('tools/release-train-shape.sh (mmnto-ai/totem#2779)', () => {
65
+ let repo = '';
66
+ beforeAll(() => {
67
+ repo = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-release-train-'));
68
+ git(repo, 'init', '-q', '-b', 'main');
69
+ git(repo, 'config', 'user.email', 'test@example.invalid');
70
+ git(repo, 'config', 'user.name', 'test');
71
+ git(repo, 'config', 'commit.gpgsign', 'false');
72
+ git(repo, 'config', 'core.autocrlf', 'false');
73
+ // The base: two pending changesets, the README and config beside them, one
74
+ // workspace package with a CHANGELOG, one without (its first release ADDS
75
+ // one), the private root package.json, an owed doctrine file, and a path
76
+ // carrying whitespace.
77
+ write(repo, '.changeset/README.md', 'readme');
78
+ write(repo, '.changeset/config.json', '{}');
79
+ write(repo, '.changeset/a.md', CHANGESET_A);
80
+ write(repo, '.changeset/b.md', 'b');
81
+ write(repo, 'packages/x/package.json', '{"version":"1.0.0"}');
82
+ write(repo, 'packages/x/CHANGELOG.md', '# x');
83
+ write(repo, 'packages/y/package.json', '{"version":"0.0.0"}');
84
+ write(repo, 'package.json', '{"private":true}');
85
+ write(repo, 'doctrine/d.md', 'd');
86
+ write(repo, 'packages/x/CHANGELOG.md more', 'space');
87
+ git(repo, 'add', '-A');
88
+ git(repo, 'commit', '-q', '-m', 'base');
89
+ git(repo, 'branch', 'base');
90
+ // A second base with nothing pending.
91
+ git(repo, 'checkout', '-q', '-b', 'base-empty');
92
+ fs.rmSync(path.join(repo, '.changeset/a.md'));
93
+ fs.rmSync(path.join(repo, '.changeset/b.md'));
94
+ git(repo, 'add', '-A');
95
+ git(repo, 'commit', '-q', '-m', 'base-empty');
96
+ git(repo, 'checkout', '-q', 'base');
97
+ });
98
+ afterAll(() => {
99
+ if (repo.length > 0)
100
+ fs.rmSync(repo, { recursive: true, force: true });
101
+ });
102
+ let caseNo = 0;
103
+ /** Branch from `from`, apply `mutate`, commit, and run the script against `from`. */
104
+ function scenario(from, mutate) {
105
+ caseNo += 1;
106
+ git(repo, 'checkout', '-q', '-b', `case-${caseNo}`, from);
107
+ mutate();
108
+ git(repo, 'add', '-A');
109
+ git(repo, 'commit', '-q', '--allow-empty', '-m', `case ${caseNo}`);
110
+ return shape(repo, from);
111
+ }
112
+ const consumeAll = () => {
113
+ fs.rmSync(path.join(repo, '.changeset/a.md'));
114
+ fs.rmSync(path.join(repo, '.changeset/b.md'));
115
+ };
116
+ const renderX = () => {
117
+ write(repo, 'packages/x/CHANGELOG.md', '# x\n\n## 1.1.0');
118
+ write(repo, 'packages/x/package.json', '{"version":"1.1.0"}');
119
+ };
120
+ it('the release train: every pending changeset consumed, CHANGELOG and package.json rendered — exempt', () => {
121
+ const r = scenario('base', () => {
122
+ consumeAll();
123
+ renderX();
124
+ });
125
+ expect(r.status).toBe(0);
126
+ expect(r.exempt).toBe('exempt=true');
127
+ expect(r.log).toContain('::notice title=totem legs gate SKIPPED::');
128
+ });
129
+ it("a new package's first release ADDS its CHANGELOG (mmnto-ai/totem#2514), even one rename detection would pair with the deleted changeset — exempt", () => {
130
+ const r = scenario('base', () => {
131
+ consumeAll();
132
+ write(repo, 'packages/y/CHANGELOG.md', CHANGESET_A);
133
+ write(repo, 'packages/y/package.json', '{"version":"0.1.0"}');
134
+ });
135
+ expect(r.status).toBe(0);
136
+ expect(r.exempt).toBe('exempt=true');
137
+ expect(r.log).not.toContain('paths outside the shape');
138
+ });
139
+ it('nothing pending at the merge base, yet CHANGELOG and package.json rewritten — not exempt (the train writes nothing when it has nothing to consume)', () => {
140
+ const r = scenario('base-empty', () => {
141
+ write(repo, 'packages/x/CHANGELOG.md', '# TOTALLY FABRICATED');
142
+ write(repo, 'packages/x/package.json', '{"version":"9.9.9"}');
143
+ });
144
+ expect(r.status).toBe(0);
145
+ expect(r.exempt).toBe('exempt=false');
146
+ expect(r.log).toContain('nothing pending at the merge base');
147
+ });
148
+ it('the script parses (bash -n) — the only shell gate this repo runs on it', () => {
149
+ const r = spawnSync('bash', ['-n', SCRIPT], { encoding: 'utf-8' });
150
+ expect(r.status).toBe(0);
151
+ });
152
+ it('deleting a SUBSET of the pending changesets (the bypass Greptile named) — not exempt', () => {
153
+ const r = scenario('base', () => {
154
+ fs.rmSync(path.join(repo, '.changeset/a.md'));
155
+ renderX();
156
+ });
157
+ expect(r.status).toBe(0);
158
+ expect(r.exempt).toBe('exempt=false');
159
+ expect(r.log).toContain('not exactly the pending set');
160
+ });
161
+ it('deletions only, nothing rendered — not exempt', () => {
162
+ const r = scenario('base', consumeAll);
163
+ expect(r.status).toBe(0);
164
+ expect(r.exempt).toBe('exempt=false');
165
+ expect(r.log).toContain('no CHANGELOG rendered');
166
+ });
167
+ it('deleting .changeset/README.md beside the train — not exempt', () => {
168
+ const r = scenario('base', () => {
169
+ consumeAll();
170
+ renderX();
171
+ fs.rmSync(path.join(repo, '.changeset/README.md'));
172
+ });
173
+ expect(r.status).toBe(0);
174
+ expect(r.exempt).toBe('exempt=false');
175
+ // README is never pending, so its deletion is a mismatch of rule 2.
176
+ expect(r.log).toContain('not exactly the pending set');
177
+ });
178
+ it('the train plus an added owed doctrine file — not exempt', () => {
179
+ const r = scenario('base', () => {
180
+ consumeAll();
181
+ renderX();
182
+ write(repo, 'doctrine/new-rule.md', 'new');
183
+ });
184
+ expect(r.exempt).toBe('exempt=false');
185
+ expect(r.log).toContain('doctrine/new-rule.md');
186
+ });
187
+ it('the train plus the private root package.json — not exempt', () => {
188
+ const r = scenario('base', () => {
189
+ consumeAll();
190
+ renderX();
191
+ write(repo, 'package.json', '{"private":true,"x":1}');
192
+ });
193
+ expect(r.status).toBe(0);
194
+ expect(r.exempt).toBe('exempt=false');
195
+ expect(r.log).toContain('paths outside the shape');
196
+ expect(r.log).toMatch(/^M\s+package\.json$/m);
197
+ });
198
+ it('the train plus a path carrying whitespace — not exempt', () => {
199
+ const r = scenario('base', () => {
200
+ consumeAll();
201
+ renderX();
202
+ write(repo, 'packages/x/CHANGELOG.md more', 'changed');
203
+ });
204
+ expect(r.status).toBe(0);
205
+ expect(r.exempt).toBe('exempt=false');
206
+ expect(r.log).toContain('paths outside the shape');
207
+ expect(r.log).toContain('packages/x/CHANGELOG.md more');
208
+ });
209
+ it('an empty diff while changesets are pending — not exempt', () => {
210
+ const r = scenario('base', () => undefined);
211
+ expect(r.status).toBe(0);
212
+ expect(r.exempt).toBe('exempt=false');
213
+ expect(r.log).toContain('not exactly the pending set');
214
+ });
215
+ it('an empty diff with nothing pending — exempt (nothing to judge)', () => {
216
+ const r = scenario('base-empty', () => undefined);
217
+ expect(r.status).toBe(0);
218
+ expect(r.exempt).toBe('exempt=true');
219
+ });
220
+ it('a base ref that does not resolve — the derivation fails, nothing is written (fail-closed)', () => {
221
+ const r = shape(repo, 'no-such-ref');
222
+ expect(r.status).not.toBe(0);
223
+ expect(r.exempt).toBe('');
224
+ });
225
+ });
226
+ //# sourceMappingURL=release-train-shape.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release-train-shape.test.js","sourceRoot":"","sources":["../../src/commands/release-train-shape.test.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { spawnSync } from 'node:child_process';
1
2
  import * as fs from 'node:fs';
2
3
  import * as path from 'node:path';
3
4
  import { fileURLToPath } from 'node:url';
@@ -24,6 +25,94 @@ const RENDER = { tier: TIER, totemDir: TOTEM_DIR, fallbackCmd: FALLBACK_CMD };
24
25
  function readTool(hook) {
25
26
  return fs.readFileSync(path.join(TOOLS_DIR, hook), 'utf-8');
26
27
  }
28
+ // Named once: the newline is BUILT, never authored as an escape (the banked
29
+ // heredoc decode trap), and `indexOf`'s -1 reads as a sentinel only by
30
+ // convention (CodeRabbit on mmnto-ai/totem#2780).
31
+ const LF = String.fromCharCode(10);
32
+ const NOT_FOUND = -1;
33
+ const GATE_STEP_NAME = 'totem legs gate (HARD';
34
+ const RELEASE_TRAIN_STEP_NAME = 'release-train check';
35
+ function readLintWorkflow() {
36
+ return fs.readFileSync(path.join(REPO_ROOT, '.github', 'workflows', 'lint.yml'), 'utf-8');
37
+ }
38
+ /**
39
+ * ONE step's YAML — from the `- name:` line whose name starts with `namePrefix`
40
+ * to the next step's `- name:` — comment lines dropped: the NEXT step's leading
41
+ * comment sits inside the slice and legitimately discusses `continue-on-error`;
42
+ * only YAML keys count. Empty when no step carries that name. Every assertion
43
+ * on a step's own `if:` / `id:` / `run:` reads its slice, never the whole
44
+ * workflow, so a decoy elsewhere cannot satisfy it (Greptile P2 and CodeRabbit
45
+ * on mmnto-ai/totem#2780).
46
+ */
47
+ function stepNamed(workflow, namePrefix) {
48
+ // Scoped to the `lint` job and comments dropped FIRST, so neither a `#` line
49
+ // carrying `- name: …` (the leg's F7 on mmnto-ai/totem#2780) nor a decoy step
50
+ // in another job (CodeRabbit, same PR) can anchor the slice; and EXACTLY one
51
+ // step may carry the name — a second one would let the wrong step satisfy
52
+ // every assertion below.
53
+ const job = lintJobOf(workflow);
54
+ const marker = '- name: ' + namePrefix;
55
+ const stepStart = job.indexOf(marker);
56
+ if (stepStart === NOT_FOUND)
57
+ return '';
58
+ if (job.indexOf(marker, stepStart + 1) !== NOT_FOUND)
59
+ return '';
60
+ const nextStep = job.indexOf('- name:', stepStart + 1);
61
+ return job.slice(stepStart, nextStep === NOT_FOUND ? job.length : nextStep);
62
+ }
63
+ /**
64
+ * The `lint` job's YAML — from its key to the next top-level job key (two-space
65
+ * indent) or the end of the file — comment lines dropped. Empty when the job is
66
+ * missing. Every step and header lookup reads this slice, never the file.
67
+ */
68
+ function lintJobOf(workflow) {
69
+ const clean = dropComments(workflow);
70
+ const jobIdx = clean.indexOf(LF + ' lint:' + LF);
71
+ if (jobIdx === NOT_FOUND)
72
+ return '';
73
+ const nextJob = clean.slice(jobIdx + 1).search(/\n {2}[A-Za-z0-9_-]+:\n/);
74
+ return clean.slice(jobIdx, nextJob === NOT_FOUND ? clean.length : jobIdx + 1 + nextJob);
75
+ }
76
+ /** The non-empty, trimmed lines of a step's `run: |` block, in order. */
77
+ function runLinesOf(step) {
78
+ const runIdx = step.indexOf('run: |');
79
+ if (runIdx === NOT_FOUND)
80
+ return [];
81
+ return step
82
+ .slice(runIdx + 'run: |'.length)
83
+ .split(LF)
84
+ .map((line) => line.trim())
85
+ .filter((line) => line.length > 0);
86
+ }
87
+ function gateStepOf(workflow) {
88
+ return stepNamed(workflow, GATE_STEP_NAME);
89
+ }
90
+ /** The `if:` lines of one step slice, trimmed — exactly one is the contract. */
91
+ function ifLinesOf(step) {
92
+ return step
93
+ .split(LF)
94
+ .map((line) => line.trim())
95
+ .filter((line) => line.startsWith('if:'));
96
+ }
97
+ /**
98
+ * The `lint` job's header — from its key to its `steps:` — comment lines
99
+ * dropped, so an assertion on the job-level env predicate reads YAML keys and
100
+ * never a `#` decoy carrying the same text (the leg's F3 on
101
+ * mmnto-ai/totem#2780). Empty when the job or its steps key is missing.
102
+ */
103
+ function lintJobHeaderOf(workflow) {
104
+ const job = lintJobOf(workflow);
105
+ const stepsIdx = job.indexOf(LF + ' steps:');
106
+ if (stepsIdx === NOT_FOUND)
107
+ return '';
108
+ return job.slice(0, stepsIdx);
109
+ }
110
+ function dropComments(yaml) {
111
+ return yaml
112
+ .split(LF)
113
+ .filter((line) => !line.trim().startsWith('#'))
114
+ .join(LF);
115
+ }
27
116
  describe('tools/ hook scripts match the CLI template builders (mmnto-ai/totem#2404)', () => {
28
117
  it('resolves the pnpm-based fallback command for this repo', () => {
29
118
  expect(FALLBACK_CMD).toBe('pnpm dlx @mmnto/cli');
@@ -38,4 +127,115 @@ describe('tools/ hook scripts match the CLI template builders (mmnto-ai/totem#24
38
127
  expect(readTool('post-merge')).toBe(buildHookContent(RENDER));
39
128
  });
40
129
  });
130
+ // The repo's own legs-gate CI arm (mmnto-ai/totem#2771; the `legs-gate` parity
131
+ // row, arm (a)). Two things the arm depends on that nothing else asserts: the
132
+ // artifacts ignore must RE-INCLUDE the deposit directory, or every deposit is
133
+ // unstageable and the required `Totem Lint` check goes permanently red; and
134
+ // the workflow step must invoke the BUILT entry before the lint step's
135
+ // `--depth=1` fetch, which shallows the checkout. Both are read from the repo's
136
+ // files, so an edit that narrows the ignore or moves the step fails here first.
137
+ describe("the repo's own legs-gate CI arm posture (mmnto-ai/totem#2771)", () => {
138
+ const gitOk = spawnSync('git', ['--version'], { encoding: 'utf-8' }).status === 0;
139
+ const checkIgnore = (relPath) => spawnSync('git', ['check-ignore', '-q', relPath], { cwd: REPO_ROOT, encoding: 'utf-8' }).status;
140
+ it.skipIf(!gitOk)('.gitignore re-includes .totem/artifacts/legs/ and keeps its siblings ignored', () => {
141
+ // check-ignore: 0 = ignored, 1 = not ignored. A deposit path must be stageable.
142
+ expect(checkIgnore('.totem/artifacts/legs/' + 'a'.repeat(40) + '.json')).toBe(1);
143
+ expect(checkIgnore('.totem/artifacts/runs/x.json')).toBe(0);
144
+ expect(checkIgnore('.totem/artifacts/panels/x.json')).toBe(0);
145
+ expect(checkIgnore('.totem/artifacts/verdicts/x.json')).toBe(0);
146
+ });
147
+ it("this repo's config never softens the CI arm: hooks.legsOwed.enforce is not 'advisory' here", () => {
148
+ // The gate reads the knob in CI too, so `enforce: 'advisory'` would turn the
149
+ // hard step into a green no-op while the row's presence checks still read
150
+ // arm (a) as adopted (the leg's F5; Greptile on mmnto-ai/totem#2772). A repo
151
+ // adopting the CI arm leaves the knob unset or sets 'block'; this pins ours.
152
+ const config = fs.readFileSync(path.join(REPO_ROOT, 'totem.config.ts'), 'utf-8');
153
+ expect(config).not.toMatch(/enforce\s*:\s*['"]advisory['"]/);
154
+ });
155
+ it('the lint workflow runs the built gate as a hard step before the --depth=1 fetch', () => {
156
+ const workflow = readLintWorkflow();
157
+ const gateIdx = workflow.indexOf('node packages/cli/dist/index.js legs gate');
158
+ // The fetch COMMAND, not the flag: the gate step's own comment names
159
+ // `--depth=1` while explaining why it runs first.
160
+ const shallowIdx = workflow.indexOf('git fetch origin "$BASE_REF" --depth=1');
161
+ expect(gateIdx).toBeGreaterThan(NOT_FOUND);
162
+ expect(shallowIdx).toBeGreaterThan(NOT_FOUND);
163
+ expect(gateIdx).toBeLessThan(shallowIdx);
164
+ // The bin-shim form cannot resolve in a job that builds the CLI it calls.
165
+ expect(workflow).not.toContain('pnpm exec totem legs gate');
166
+ // The WHOLE step, from its name to the next step's: the base fetch precedes
167
+ // the gate inside it, and no continue-on-error sits anywhere in it — a
168
+ // trailing one after `run:` would soften the step just as well.
169
+ const step = gateStepOf(workflow);
170
+ const baseFetchIdx = step.indexOf('git fetch origin "$BASE_REF"');
171
+ expect(baseFetchIdx).toBeGreaterThan(NOT_FOUND);
172
+ expect(baseFetchIdx).toBeLessThan(step.indexOf('node packages/cli/dist/index.js legs gate'));
173
+ expect(step).not.toContain('--depth=1');
174
+ expect(step).not.toContain('continue-on-error');
175
+ // The run block is EXACTLY the base fetch and the bare gate: a `|| true`, a
176
+ // `;` or a trailing `&&` on the gate line would neuter it while every
177
+ // assertion above still passes (the leg's F7 on mmnto-ai/totem#2780).
178
+ expect(runLinesOf(step)).toEqual([
179
+ 'git fetch origin "$BASE_REF"',
180
+ 'node packages/cli/dist/index.js legs gate',
181
+ ]);
182
+ });
183
+ it('the gate step is skipped ONLY for the release train — exact branch name AND same repository AND the release-train diff shape — and either outcome is announced (mmnto-ai/totem#2779)', () => {
184
+ const workflow = readLintWorkflow();
185
+ // The branch SELECTOR is defined ONCE, at job level, and the assertion reads
186
+ // the job header with comments dropped — a `#` decoy carrying the same text
187
+ // must not satisfy it. Both halves are load-bearing: the exact name (a
188
+ // prefix test is widenable by any author; GitHub's `==` and `startsWith`
189
+ // are both case-insensitive, so case is not the distinction) and the same
190
+ // repository (a fork's `head_ref` is the bare branch name).
191
+ const header = lintJobHeaderOf(workflow);
192
+ expect(header).not.toBe('');
193
+ expect(header).toContain("LEGS_GATE_RELEASE_TRAIN: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.head_ref == 'changeset-release/main' }}");
194
+ // The selector only picks the branch. The check step, gated on it, derives
195
+ // the diff's SHAPE and exempts only what the action writes — a deleted
196
+ // .changeset/*.md (never its README), an added or modified
197
+ // packages/<pkg>/CHANGELOG.md (a new package's first release ADDS one:
198
+ // mmnto-ai/totem#2514), a modified packages/<pkg>/package.json — so a
199
+ // writer who pushes anything else onto the release branch meets the gate
200
+ // (Greptile's P1 on mmnto-ai/totem#2780). Every assertion reads the check
201
+ // step's own slice, and the run block is pinned EXACTLY, in order: which
202
+ // branch writes `exempt=true` is the mechanism (swapping the branches
203
+ // passed a looser test — the leg's F3), `set -euo pipefail` under
204
+ // `shell: bash` is what makes a failed diff abort instead of reading as
205
+ // an empty, in-shape list (F2), the whole-line anchors are what keep a
206
+ // whitespace-carrying path out of shape (F4), and every filter consumes
207
+ // its whole input — a `grep -q` under pipefail SIGPIPEs the echo on a
208
+ // large diff and the README guard reads false (pass-4 F1).
209
+ const check = stepNamed(workflow, RELEASE_TRAIN_STEP_NAME);
210
+ expect(check).not.toBe('');
211
+ expect(check).toContain('id: release_train');
212
+ expect(check).toContain('shell: bash');
213
+ expect(ifLinesOf(check)).toEqual(["if: ${{ env.LEGS_GATE_RELEASE_TRAIN == 'true' }}"]);
214
+ // The run block is EXACTLY the base fetch and the script, in that order,
215
+ // under `set -euo pipefail`; the script's own rules are executed case by
216
+ // case in release-train-shape.test.ts, and its whole text is a tracked
217
+ // file, so what this pin buys is that the step runs THAT script and nothing
218
+ // softens or bypasses it here.
219
+ expect(runLinesOf(check)).toEqual([
220
+ 'set -euo pipefail',
221
+ 'git fetch origin "$BASE_REF"',
222
+ 'bash tools/release-train-shape.sh "origin/$BASE_REF"',
223
+ ]);
224
+ expect(check).not.toContain('continue-on-error');
225
+ expect(fs.existsSync(path.join(REPO_ROOT, 'tools', 'release-train-shape.sh'))).toBe(true);
226
+ // The gate reads the check's OUTPUT, and exactly that: a skipped check
227
+ // writes no output and '' != 'true' runs the gate — fail-closed. Any other
228
+ // condition — a prefix match, `if: false`, a value the PR author controls —
229
+ // softens the floor while `Totem Lint` stays green, which the
230
+ // `continue-on-error` assertion above cannot see.
231
+ const step = gateStepOf(workflow);
232
+ expect(step).not.toBe('');
233
+ expect(ifLinesOf(step)).toEqual(["if: ${{ steps.release_train.outputs.exempt != 'true' }}"]);
234
+ expect(step).not.toContain('startsWith(');
235
+ // The check precedes the gate — an output read before it is written is ''.
236
+ const checkIdx = workflow.indexOf('- name: ' + RELEASE_TRAIN_STEP_NAME);
237
+ expect(checkIdx).toBeGreaterThan(NOT_FOUND);
238
+ expect(checkIdx).toBeLessThan(workflow.indexOf('- name: ' + GATE_STEP_NAME));
239
+ });
240
+ });
41
241
  //# sourceMappingURL=tools-hook-parity.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools-hook-parity.test.js","sourceRoot":"","sources":["../../src/commands/tools-hook-parity.test.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAE5B,oFAAoF;AACpF,sFAAsF;AACtF,oFAAoF;AACpF,uFAAuF;AACvF,kFAAkF;AAClF,qFAAqF;AACrF,wEAAwE;AAExE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;AAC5F,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AAEhD,uFAAuF;AACvF,mFAAmF;AACnF,+DAA+D;AAC/D,MAAM,IAAI,GAAG,UAAmB,CAAC;AACjC,qFAAqF;AACrF,kFAAkF;AAClF,MAAM,SAAS,GAAG,QAAQ,CAAC;AAC3B,MAAM,YAAY,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;AACnD,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;AAE9E,SAAS,QAAQ,CAAC,IAAY;IAC5B,OAAO,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC;AAED,QAAQ,CAAC,2EAA2E,EAAE,GAAG,EAAE;IACzF,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"tools-hook-parity.test.js","sourceRoot":"","sources":["../../src/commands/tools-hook-parity.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmnto/cli",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Totem: local-first toolkit that keeps AI-agent work queryable, enforceable, and derivable as plain files in your codebase",
5
5
  "type": "module",
6
6
  "engines": {
@@ -28,7 +28,7 @@
28
28
  "smol-toml": "^1.6.1",
29
29
  "yaml": "^2.4.0",
30
30
  "zod": "^3.24.0",
31
- "@mmnto/totem": "2.0.0"
31
+ "@mmnto/totem": "2.1.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@anthropic-ai/sdk": "^0.98.0",