@mmnto/cli 2.1.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/commands/doctor-parity-fetch.d.ts +93 -2
  2. package/dist/commands/doctor-parity-fetch.d.ts.map +1 -1
  3. package/dist/commands/doctor-parity-fetch.js +332 -11
  4. package/dist/commands/doctor-parity-fetch.js.map +1 -1
  5. package/dist/commands/doctor-parity-fetch.test.d.ts +6 -0
  6. package/dist/commands/doctor-parity-fetch.test.d.ts.map +1 -1
  7. package/dist/commands/doctor-parity-fetch.test.js +405 -1
  8. package/dist/commands/doctor-parity-fetch.test.js.map +1 -1
  9. package/dist/commands/doctor-parity.d.ts.map +1 -1
  10. package/dist/commands/doctor-parity.js +27 -1
  11. package/dist/commands/doctor-parity.js.map +1 -1
  12. package/dist/commands/doctor-parity.test.js +67 -0
  13. package/dist/commands/doctor-parity.test.js.map +1 -1
  14. package/dist/commands/eject-gate-needle.test.d.ts +2 -0
  15. package/dist/commands/eject-gate-needle.test.d.ts.map +1 -0
  16. package/dist/commands/eject-gate-needle.test.js +48 -0
  17. package/dist/commands/eject-gate-needle.test.js.map +1 -0
  18. package/dist/commands/eject.d.ts +10 -0
  19. package/dist/commands/eject.d.ts.map +1 -1
  20. package/dist/commands/eject.js +67 -12
  21. package/dist/commands/eject.js.map +1 -1
  22. package/dist/commands/eject.test.js +113 -0
  23. package/dist/commands/eject.test.js.map +1 -1
  24. package/dist/commands/gate-install.d.ts +55 -5
  25. package/dist/commands/gate-install.d.ts.map +1 -1
  26. package/dist/commands/gate-install.js +47 -34
  27. package/dist/commands/gate-install.js.map +1 -1
  28. package/dist/commands/gate-install.test.js +514 -56
  29. package/dist/commands/gate-install.test.js.map +1 -1
  30. package/dist/commands/gate.d.ts +24 -9
  31. package/dist/commands/gate.d.ts.map +1 -1
  32. package/dist/commands/gate.js +50 -18
  33. package/dist/commands/gate.js.map +1 -1
  34. package/dist/commands/gate.test.js +74 -3
  35. package/dist/commands/gate.test.js.map +1 -1
  36. package/dist/commands/host-hooks.d.ts +8 -2
  37. package/dist/commands/host-hooks.d.ts.map +1 -1
  38. package/dist/commands/host-hooks.js.map +1 -1
  39. package/dist/commands/init-templates.d.ts +4 -4
  40. package/dist/commands/init-templates.d.ts.map +1 -1
  41. package/dist/commands/init-templates.js +243 -63
  42. package/dist/commands/init-templates.js.map +1 -1
  43. package/dist/commands/init.d.ts.map +1 -1
  44. package/dist/commands/init.js +35 -17
  45. package/dist/commands/init.js.map +1 -1
  46. package/dist/commands/init.test.js +10 -0
  47. package/dist/commands/init.test.js.map +1 -1
  48. package/dist/commands/session-context-journal-select.test.d.ts +2 -0
  49. package/dist/commands/session-context-journal-select.test.d.ts.map +1 -0
  50. package/dist/commands/session-context-journal-select.test.js +95 -0
  51. package/dist/commands/session-context-journal-select.test.js.map +1 -0
  52. package/dist/commands/sync-labels-forms.test.d.ts +33 -0
  53. package/dist/commands/sync-labels-forms.test.d.ts.map +1 -0
  54. package/dist/commands/sync-labels-forms.test.js +325 -0
  55. package/dist/commands/sync-labels-forms.test.js.map +1 -0
  56. package/dist/index.js +2 -2
  57. package/dist/index.js.map +1 -1
  58. package/package.json +2 -2
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=session-context-journal-select.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-context-journal-select.test.d.ts","sourceRoot":"","sources":["../../src/commands/session-context-journal-select.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,95 @@
1
+ import path from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { beforeAll, describe, expect, it } from 'vitest';
4
+ const repoRoot = path.resolve(__dirname, '..', '..', '..', '..');
5
+ const helperPath = path.join(repoRoot, '.claude', 'hooks', 'lib', 'select-latest-journal.mjs');
6
+ let selectLatestJournal;
7
+ beforeAll(async () => {
8
+ const mod = (await import(pathToFileURL(helperPath).href));
9
+ selectLatestJournal = mod.selectLatestJournal;
10
+ });
11
+ /** A stat function over a fixed mtime table; a missing key throws like fs would. */
12
+ function statOf(table) {
13
+ return (file) => {
14
+ if (!(file in table)) {
15
+ throw new Error(`ENOENT: no such file or directory, stat '${file}'`);
16
+ }
17
+ return table[file];
18
+ };
19
+ }
20
+ const T1 = 1_000;
21
+ const T2 = 2_000;
22
+ const T3 = 3_000;
23
+ describe('selectLatestJournal — the recency policy behind the hook', () => {
24
+ it('a conforming directory (counter order == write order) serves the lexical-newest with no drift', () => {
25
+ const pick = selectLatestJournal(['claude-0001-a.md', 'claude-0002-b.md', 'claude-0003-c.md'], statOf({
26
+ 'claude-0001-a.md': T1,
27
+ 'claude-0002-b.md': T2,
28
+ 'claude-0003-c.md': T3,
29
+ }));
30
+ expect(pick).not.toBeNull();
31
+ expect(pick.latest).toBe('claude-0003-c.md');
32
+ expect(pick.lexicalLatest).toBe('claude-0003-c.md');
33
+ expect(pick.drift).toBe(false);
34
+ expect(pick.reason).toBe('lexical');
35
+ expect(pick.statFailures).toEqual([]);
36
+ });
37
+ it('a clock-named file that out-sorts a later write is naming drift: the newer write is served and named', () => {
38
+ // The 2026-09-07 incident shape: `claude-2315-…` (a 23:15 clock stamp) out-sorted the
39
+ // 01:33 signoff `claude-0132-…`; the pre-fix hook served the stale file silently.
40
+ const pick = selectLatestJournal(['claude-0001-first.md', 'claude-2315-clock-named.md', 'claude-0100-newest-write.md'], statOf({
41
+ 'claude-0001-first.md': T1,
42
+ 'claude-2315-clock-named.md': T2,
43
+ 'claude-0100-newest-write.md': T3,
44
+ }));
45
+ expect(pick.lexicalLatest).toBe('claude-2315-clock-named.md');
46
+ expect(pick.mtimeLatest).toBe('claude-0100-newest-write.md');
47
+ expect(pick.latest).toBe('claude-0100-newest-write.md');
48
+ expect(pick.drift).toBe(true);
49
+ expect(pick.reason).toBe('newest-write');
50
+ });
51
+ it('an mtime tie keeps the lexical pick (no drift reported)', () => {
52
+ const pick = selectLatestJournal(['claude-0001-a.md', 'claude-0002-b.md'], statOf({
53
+ 'claude-0001-a.md': T2,
54
+ 'claude-0002-b.md': T2,
55
+ }));
56
+ expect(pick.latest).toBe('claude-0002-b.md');
57
+ expect(pick.drift).toBe(false);
58
+ });
59
+ it('a sibling whose stat throws is skipped and reported — it never drops the pick', () => {
60
+ // The GCA / Greptile / CodeRabbit round-1 finding: pre-fix, one dangling entry threw
61
+ // inside the hook's outer try and the whole journal block was lost.
62
+ const pick = selectLatestJournal(['claude-0001-a.md', 'claude-0002-dangling.md', 'claude-0003-c.md'], statOf({ 'claude-0001-a.md': T1, 'claude-0003-c.md': T3 }));
63
+ expect(pick.latest).toBe('claude-0003-c.md');
64
+ expect(pick.drift).toBe(false);
65
+ expect(pick.reason).toBe('lexical');
66
+ expect(pick.statFailures).toHaveLength(1);
67
+ expect(pick.statFailures[0].file).toBe('claude-0002-dangling.md');
68
+ expect(pick.statFailures[0].message).toContain('ENOENT');
69
+ });
70
+ it('when the lexical-newest itself cannot be stat-ed, the newest readable write is served and the reason says so, not drift', () => {
71
+ const pick = selectLatestJournal(['claude-0001-a.md', 'claude-0002-b.md', 'claude-0003-gone.md'], statOf({
72
+ 'claude-0001-a.md': T1,
73
+ 'claude-0002-b.md': T2,
74
+ }));
75
+ expect(pick.lexicalLatest).toBe('claude-0003-gone.md');
76
+ expect(pick.latest).toBe('claude-0002-b.md');
77
+ expect(pick.drift).toBe(false);
78
+ expect(pick.reason).toBe('lexical-unreadable');
79
+ expect(pick.statFailures.map((s) => s.file)).toEqual(['claude-0003-gone.md']);
80
+ });
81
+ it('when nothing can be stat-ed the lexical pick stands with every failure reported', () => {
82
+ const pick = selectLatestJournal(['claude-0001-a.md', 'claude-0002-b.md'], statOf({}));
83
+ expect(pick.latest).toBe('claude-0002-b.md');
84
+ expect(pick.reason).toBe('lexical');
85
+ expect(pick.drift).toBe(false);
86
+ expect(pick.statFailures).toHaveLength(2);
87
+ });
88
+ it('filters non-markdown entries and returns null for an empty directory', () => {
89
+ expect(selectLatestJournal(['legacy', 'notes.txt'], statOf({}))).toBeNull();
90
+ const pick = selectLatestJournal(['legacy', 'claude-0001-a.md'], statOf({ 'claude-0001-a.md': T1 }));
91
+ expect(pick.files).toEqual(['claude-0001-a.md']);
92
+ expect(pick.latest).toBe('claude-0001-a.md');
93
+ });
94
+ });
95
+ //# sourceMappingURL=session-context-journal-select.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-context-journal-select.test.js","sourceRoot":"","sources":["../../src/commands/session-context-journal-select.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAwBzD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AACjE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,2BAA2B,CAAC,CAAC;AAE/F,IAAI,mBAAwC,CAAC;AAE7C,SAAS,CAAC,KAAK,IAAI,EAAE;IACnB,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAExD,CAAC;IACF,mBAAmB,GAAG,GAAG,CAAC,mBAAmB,CAAC;AAChD,CAAC,CAAC,CAAC;AAEH,oFAAoF;AACpF,SAAS,MAAM,CAAC,KAA6B;IAC3C,OAAO,CAAC,IAAI,EAAE,EAAE;QACd,IAAI,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,4CAA4C,IAAI,GAAG,CAAC,CAAC;QACvE,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAE,CAAC;IACtB,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,EAAE,GAAG,KAAK,CAAC;AACjB,MAAM,EAAE,GAAG,KAAK,CAAC;AACjB,MAAM,EAAE,GAAG,KAAK,CAAC;AAEjB,QAAQ,CAAC,0DAA0D,EAAE,GAAG,EAAE;IACxE,EAAE,CAAC,+FAA+F,EAAE,GAAG,EAAE;QACvG,MAAM,IAAI,GAAG,mBAAmB,CAC9B,CAAC,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,CAAC,EAC5D,MAAM,CAAC;YACL,kBAAkB,EAAE,EAAE;YACtB,kBAAkB,EAAE,EAAE;YACtB,kBAAkB,EAAE,EAAE;SACvB,CAAC,CACH,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC9C,MAAM,CAAC,IAAK,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACrD,MAAM,CAAC,IAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,IAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrC,MAAM,CAAC,IAAK,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sGAAsG,EAAE,GAAG,EAAE;QAC9G,sFAAsF;QACtF,kFAAkF;QAClF,MAAM,IAAI,GAAG,mBAAmB,CAC9B,CAAC,sBAAsB,EAAE,4BAA4B,EAAE,6BAA6B,CAAC,EACrF,MAAM,CAAC;YACL,sBAAsB,EAAE,EAAE;YAC1B,4BAA4B,EAAE,EAAE;YAChC,6BAA6B,EAAE,EAAE;SAClC,CAAC,CACH,CAAC;QACF,MAAM,CAAC,IAAK,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAC/D,MAAM,CAAC,IAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAC9D,MAAM,CAAC,IAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QACzD,MAAM,CAAC,IAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM,CAAC,IAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,IAAI,GAAG,mBAAmB,CAC9B,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,EACxC,MAAM,CAAC;YACL,kBAAkB,EAAE,EAAE;YACtB,kBAAkB,EAAE,EAAE;SACvB,CAAC,CACH,CAAC;QACF,MAAM,CAAC,IAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC9C,MAAM,CAAC,IAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+EAA+E,EAAE,GAAG,EAAE;QACvF,qFAAqF;QACrF,oEAAoE;QACpE,MAAM,IAAI,GAAG,mBAAmB,CAC9B,CAAC,kBAAkB,EAAE,yBAAyB,EAAE,kBAAkB,CAAC,EACnE,MAAM,CAAC,EAAE,kBAAkB,EAAE,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,CAAC,CAC3D,CAAC;QACF,MAAM,CAAC,IAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC9C,MAAM,CAAC,IAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,IAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrC,MAAM,CAAC,IAAK,CAAC,YAAY,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAK,CAAC,YAAY,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACpE,MAAM,CAAC,IAAK,CAAC,YAAY,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yHAAyH,EAAE,GAAG,EAAE;QACjI,MAAM,IAAI,GAAG,mBAAmB,CAC9B,CAAC,kBAAkB,EAAE,kBAAkB,EAAE,qBAAqB,CAAC,EAC/D,MAAM,CAAC;YACL,kBAAkB,EAAE,EAAE;YACtB,kBAAkB,EAAE,EAAE;SACvB,CAAC,CACH,CAAC;QACF,MAAM,CAAC,IAAK,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QACxD,MAAM,CAAC,IAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC9C,MAAM,CAAC,IAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,IAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAChD,MAAM,CAAC,IAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC;IACjF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iFAAiF,EAAE,GAAG,EAAE;QACzF,MAAM,IAAI,GAAG,mBAAmB,CAAC,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;QACvF,MAAM,CAAC,IAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC9C,MAAM,CAAC,IAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrC,MAAM,CAAC,IAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,IAAK,CAAC,YAAY,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sEAAsE,EAAE,GAAG,EAAE;QAC9E,MAAM,CAAC,mBAAmB,CAAC,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC5E,MAAM,IAAI,GAAG,mBAAmB,CAC9B,CAAC,QAAQ,EAAE,kBAAkB,CAAC,EAC9B,MAAM,CAAC,EAAE,kBAAkB,EAAE,EAAE,EAAE,CAAC,CACnC,CAAC;QACF,MAAM,CAAC,IAAK,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC;QAClD,MAAM,CAAC,IAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The issue-form ↔ label-canon coupling, and the `-WhatIf` dry run of
3
+ * `scripts/sync-labels.ps1` (mmnto-ai/totem#2792).
4
+ *
5
+ * Two contracts are locked here, both DERIVED from the real script rather than
6
+ * mirrored (Tenet 20 — the same discipline `parity-label-canon.test.ts` applies
7
+ * to the canon itself):
8
+ *
9
+ * 1. Every `.github/ISSUE_TEMPLATE/*.yml` form parses and applies exactly its
10
+ * own `type: <basename>` label, a name the script defines. A space-less
11
+ * `type:bug` would leave the canon's type label missing on every issue the
12
+ * form creates (whether GitHub drops the unknown label or mints a stray
13
+ * one); here it fails the suite instead.
14
+ * 2. Every form's `Tier` and `Scope` dropdowns offer exactly the canon's
15
+ * `tier-*` names and its `scope: ` names with the namespace stripped. Both
16
+ * option sets are computed from the parsed script — never hand-listed — so
17
+ * a new scope in the script is a failing test until the forms carry it.
18
+ * The scope derivation reads ONLY names starting with `scope: `, so the
19
+ * `disposition: ` namespace can never leak into the dropdown.
20
+ *
21
+ * The last two cases spawn `pwsh` against the real script. The first proves the
22
+ * `-WhatIf` shadow prints every `gh` call and executes none (a stub `gh` sits on
23
+ * PATH and must stay untouched); the second proves the shadow does NOT leak into
24
+ * a normal run — the same stub, no `-WhatIf`, must receive the whole canon. Both
25
+ * run against `-Repo example/stub`; neither can reach GitHub, because `gh` on the
26
+ * child's PATH is a log-appending stub.
27
+ *
28
+ * Every case skips when the repo-root files are absent (a packaged install
29
+ * carries neither the script nor `.github/`), and the spawning ones skip when
30
+ * `pwsh` is not on PATH.
31
+ */
32
+ export {};
33
+ //# sourceMappingURL=sync-labels-forms.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-labels-forms.test.d.ts","sourceRoot":"","sources":["../../src/commands/sync-labels-forms.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG"}
@@ -0,0 +1,325 @@
1
+ /**
2
+ * The issue-form ↔ label-canon coupling, and the `-WhatIf` dry run of
3
+ * `scripts/sync-labels.ps1` (mmnto-ai/totem#2792).
4
+ *
5
+ * Two contracts are locked here, both DERIVED from the real script rather than
6
+ * mirrored (Tenet 20 — the same discipline `parity-label-canon.test.ts` applies
7
+ * to the canon itself):
8
+ *
9
+ * 1. Every `.github/ISSUE_TEMPLATE/*.yml` form parses and applies exactly its
10
+ * own `type: <basename>` label, a name the script defines. A space-less
11
+ * `type:bug` would leave the canon's type label missing on every issue the
12
+ * form creates (whether GitHub drops the unknown label or mints a stray
13
+ * one); here it fails the suite instead.
14
+ * 2. Every form's `Tier` and `Scope` dropdowns offer exactly the canon's
15
+ * `tier-*` names and its `scope: ` names with the namespace stripped. Both
16
+ * option sets are computed from the parsed script — never hand-listed — so
17
+ * a new scope in the script is a failing test until the forms carry it.
18
+ * The scope derivation reads ONLY names starting with `scope: `, so the
19
+ * `disposition: ` namespace can never leak into the dropdown.
20
+ *
21
+ * The last two cases spawn `pwsh` against the real script. The first proves the
22
+ * `-WhatIf` shadow prints every `gh` call and executes none (a stub `gh` sits on
23
+ * PATH and must stay untouched); the second proves the shadow does NOT leak into
24
+ * a normal run — the same stub, no `-WhatIf`, must receive the whole canon. Both
25
+ * run against `-Repo example/stub`; neither can reach GitHub, because `gh` on the
26
+ * child's PATH is a log-appending stub.
27
+ *
28
+ * Every case skips when the repo-root files are absent (a packaged install
29
+ * carries neither the script nor `.github/`), and the spawning ones skip when
30
+ * `pwsh` is not on PATH.
31
+ */
32
+ import { spawnSync } from 'node:child_process';
33
+ import * as fs from 'node:fs';
34
+ import * as os from 'node:os';
35
+ import * as path from 'node:path';
36
+ import { fileURLToPath } from 'node:url';
37
+ import { describe, expect, it } from 'vitest';
38
+ import { parse as parseYaml } from 'yaml';
39
+ import { parseLabelCanon } from '@mmnto/totem';
40
+ /** packages/cli/src/commands → the repo root. */
41
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..');
42
+ const SCRIPT_PATH = path.join(ROOT, 'scripts', 'sync-labels.ps1');
43
+ const TEMPLATE_DIR = path.join(ROOT, '.github', 'ISSUE_TEMPLATE');
44
+ const CONFIG_PATH = path.join(TEMPLATE_DIR, 'config.yml');
45
+ const ONBOARDING_PATH = path.join(TEMPLATE_DIR, 'onboarding.md');
46
+ const REPO_FILES_PRESENT = fs.existsSync(SCRIPT_PATH) && fs.existsSync(TEMPLATE_DIR);
47
+ /** `pwsh` on PATH and runnable — absent on a minimal container, present on every GitHub runner. */
48
+ function pwshRunnable() {
49
+ const probe = spawnSync('pwsh', ['-NoProfile', '-Command', 'exit 0'], { encoding: 'utf8' });
50
+ return probe.error === undefined && probe.status === 0;
51
+ }
52
+ const PWSH_PRESENT = REPO_FILES_PRESENT && pwshRunnable();
53
+ /** The form files, `config.yml` excluded (it is not a form). */
54
+ function formFiles() {
55
+ return fs
56
+ .readdirSync(TEMPLATE_DIR)
57
+ .filter((file) => file.endsWith('.yml') && file !== 'config.yml')
58
+ .sort();
59
+ }
60
+ function readForm(file) {
61
+ return parseYaml(fs.readFileSync(path.join(TEMPLATE_DIR, file), 'utf8'));
62
+ }
63
+ /** The canon of record, parsed from the real script exactly as the sensor parses it. */
64
+ function realCanon() {
65
+ return parseLabelCanon(fs.readFileSync(SCRIPT_PATH, 'utf8'));
66
+ }
67
+ /** The `tier-*` canonical names — the Tier dropdown's option set, derived. */
68
+ function tierOptionsFromCanon(names) {
69
+ return names.filter((name) => name.startsWith('tier-')).sort();
70
+ }
71
+ /**
72
+ * The `scope: ` canonical names with the namespace stripped — the Scope
73
+ * dropdown's option set, derived. The `scope: ` prefix is the ONLY source, so a
74
+ * `disposition: ` (or any other namespace) entry can never enter this set.
75
+ */
76
+ function scopeOptionsFromCanon(names) {
77
+ const prefix = 'scope: ';
78
+ return names
79
+ .filter((name) => name.startsWith(prefix))
80
+ .map((name) => name.slice(prefix.length))
81
+ .sort();
82
+ }
83
+ /** The one dropdown in `form.body` whose rendered label is `label`. */
84
+ function dropdownByLabel(form, label) {
85
+ return (form.body ?? []).find((field) => field.type === 'dropdown' && field.attributes?.label === label);
86
+ }
87
+ // ─── Forms ↔ canon ───────────────────────────────────────
88
+ describe.skipIf(!REPO_FILES_PRESENT)('issue forms', () => {
89
+ it('ships one form per canonical type, each parsing as YAML with a name and a description', () => {
90
+ const files = formFiles();
91
+ expect(files).toEqual([
92
+ 'bug.yml',
93
+ 'chore.yml',
94
+ 'docs.yml',
95
+ 'epic.yml',
96
+ 'feature.yml',
97
+ 'security.yml',
98
+ ]);
99
+ for (const file of files) {
100
+ const form = readForm(file);
101
+ expect(typeof form.name, file).toBe('string');
102
+ expect(typeof form.description, file).toBe('string');
103
+ expect(Array.isArray(form.body), file).toBe(true);
104
+ // Titles stay free-form — no `title:` prefix is imposed on the author.
105
+ expect(Object.keys(form), file).not.toContain('title');
106
+ }
107
+ });
108
+ it('applies exactly its own type: label, one the script defines (the space in `type: bug` is load-bearing)', () => {
109
+ const canonNames = new Set(realCanon().labels.map((label) => label.name));
110
+ // The canon must be non-empty, or this assertion would pass vacuously the
111
+ // moment the script's grammar changed (the readers' own empty-canon refusal).
112
+ expect(canonNames.size).toBeGreaterThan(0);
113
+ for (const file of formFiles()) {
114
+ const form = readForm(file);
115
+ // Charter § 4c(ii): one template per `type:` value, applying THAT label —
116
+ // derived from the filename, so `docs.yml` cannot ship `type: bug`, an
117
+ // empty list, or two type labels and still pass.
118
+ const expected = `type: ${file.replace(/\.yml$/, '')}`;
119
+ expect(form.labels, file).toEqual([expected]);
120
+ expect(canonNames.has(expected), `${file} applies ${expected}`).toBe(true);
121
+ }
122
+ });
123
+ it("carries a required Tier dropdown whose options are the canon's tier-* names", () => {
124
+ const expected = tierOptionsFromCanon(realCanon().labels.map((label) => label.name));
125
+ expect(expected.length).toBeGreaterThan(0);
126
+ for (const file of formFiles()) {
127
+ const form = readForm(file);
128
+ const tier = dropdownByLabel(form, 'Tier');
129
+ expect(tier, `${file} has a Tier dropdown`).toBeDefined();
130
+ // The id and the label are BOTH a contract with the rung-1 body sensor.
131
+ expect(tier?.id, file).toBe('tier');
132
+ expect(tier?.validations?.required, file).toBe(true);
133
+ expect([...(tier?.attributes?.options ?? [])].sort(), file).toEqual(expected);
134
+ }
135
+ });
136
+ it("carries a required multi-select Scope dropdown whose options are the canon's scope: names, namespace stripped", () => {
137
+ const expected = scopeOptionsFromCanon(realCanon().labels.map((label) => label.name));
138
+ expect(expected.length).toBeGreaterThan(0);
139
+ // Derived from `scope: ` alone — never "every label that is not a tier".
140
+ expect(expected.some((option) => option.includes('disposition'))).toBe(false);
141
+ for (const file of formFiles()) {
142
+ const form = readForm(file);
143
+ const scope = dropdownByLabel(form, 'Scope');
144
+ expect(scope, `${file} has a Scope dropdown`).toBeDefined();
145
+ expect(scope?.id, file).toBe('scope');
146
+ expect(scope?.attributes?.multiple, file).toBe(true);
147
+ expect(scope?.validations?.required, file).toBe(true);
148
+ expect([...(scope?.attributes?.options ?? [])].sort(), file).toEqual(expected);
149
+ }
150
+ });
151
+ it('renders every field under a distinct heading (the body sensor keys on heading text, never on ids)', () => {
152
+ // The epic form once carried a `Scope` textarea beside the `Scope` dropdown:
153
+ // two `### Scope` headings in one rendered body, and a heading-keyed parser
154
+ // cannot tell them apart. Labels are the contract; ids never render.
155
+ for (const file of formFiles()) {
156
+ const form = readForm(file);
157
+ const labels = (form.body ?? [])
158
+ .map((field) => field.attributes?.label)
159
+ .filter((label) => typeof label === 'string');
160
+ expect(new Set(labels).size, `${file} field labels: ${labels.join(' | ')}`).toBe(labels.length);
161
+ }
162
+ });
163
+ it('routes an exploitable vulnerability out of the public security form', () => {
164
+ const form = readForm('security.yml');
165
+ const first = (form.body ?? [])[0];
166
+ expect(first?.type).toBe('markdown');
167
+ expect(first?.attributes?.value ?? '').toMatch(/advisor/i);
168
+ });
169
+ it('disables blank issues and leaves the onboarding template in place', () => {
170
+ const config = parseYaml(fs.readFileSync(CONFIG_PATH, 'utf8'));
171
+ expect(config.blank_issues_enabled).toBe(false);
172
+ expect(fs.existsSync(ONBOARDING_PATH)).toBe(true);
173
+ // Untouched by mmnto-ai/totem#2792: still the front-matter markdown template it was.
174
+ expect(fs.readFileSync(ONBOARDING_PATH, 'utf8').startsWith('---')).toBe(true);
175
+ });
176
+ });
177
+ // ─── The pwsh dry run ────────────────────────────────────
178
+ /**
179
+ * A temp dir holding a `gh` stub that appends its whole argument line to a log
180
+ * and exits 0. Prepended to the child's PATH, it satisfies the script's `gh`
181
+ * presence check and its `gh auth status` preflight while making a real GitHub
182
+ * call impossible.
183
+ */
184
+ function makeGhStub() {
185
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-gh-stub-'));
186
+ const logPath = path.join(dir, 'gh-calls.log');
187
+ // With TOTEM_GH_STUB_FAIL set in the child's environment the stub still logs
188
+ // every call but exits 1 on `label edit` — the failure path of the script's
189
+ // mutation tally (Greptile P1 on mmnto-ai/totem#2827).
190
+ if (process.platform === 'win32') {
191
+ fs.writeFileSync(path.join(dir, 'gh.cmd'), [
192
+ '@echo off',
193
+ `>>"${logPath}" echo %*`,
194
+ 'if "%TOTEM_GH_STUB_FAIL%"=="" exit /b 0',
195
+ 'echo %* | findstr /C:"label edit" >nul',
196
+ 'if %errorlevel%==0 exit /b 1',
197
+ 'exit /b 0',
198
+ '',
199
+ ].join('\r\n'), 'utf8');
200
+ }
201
+ else {
202
+ const stub = path.join(dir, 'gh');
203
+ fs.writeFileSync(stub, [
204
+ '#!/bin/sh',
205
+ `printf '%s\\n' "$*" >> "${logPath}"`,
206
+ 'if [ -n "$TOTEM_GH_STUB_FAIL" ]; then',
207
+ ' case "$*" in *"label edit"*) exit 1 ;; esac',
208
+ 'fi',
209
+ 'exit 0',
210
+ '',
211
+ ].join('\n'), { encoding: 'utf8', mode: 0o755 });
212
+ fs.chmodSync(stub, 0o755);
213
+ }
214
+ return { dir, logPath };
215
+ }
216
+ /** `process.env` with `dir` prepended to PATH, whatever case the platform spells it. */
217
+ function envWithPathPrefix(dir) {
218
+ const env = {};
219
+ let currentPath = '';
220
+ for (const [key, value] of Object.entries(process.env)) {
221
+ if (key.toUpperCase() === 'PATH') {
222
+ currentPath = value ?? '';
223
+ continue;
224
+ }
225
+ env[key] = value;
226
+ }
227
+ env.PATH = dir + path.delimiter + currentPath;
228
+ return env;
229
+ }
230
+ function runScript(args, stubDir, extraEnv = {}) {
231
+ return spawnSync('pwsh', ['-NoProfile', '-File', SCRIPT_PATH, ...args], {
232
+ cwd: ROOT,
233
+ encoding: 'utf8',
234
+ env: { ...envWithPathPrefix(stubDir), ...extraEnv },
235
+ });
236
+ }
237
+ describe.skipIf(!PWSH_PRESENT)('scripts/sync-labels.ps1 dry run', () => {
238
+ it('-WhatIf prints every gh call and executes none', () => {
239
+ const stub = makeGhStub();
240
+ try {
241
+ const run = runScript(['-WhatIf', '-Repo', 'example/stub'], stub.dir);
242
+ expect(run.error).toBeUndefined();
243
+ expect(run.status, run.stderr).toBe(0);
244
+ const stdout = run.stdout;
245
+ expect(stdout).toContain('[WhatIf] gh label edit "disposition: done"');
246
+ const labelLines = stdout
247
+ .split(/\r?\n/)
248
+ .filter((line) => line.startsWith('[WhatIf] gh label'));
249
+ // One line per would-be call, derived from the canon rather than pinned:
250
+ // an `edit` per canonical label, a `create` per disposition label, and a
251
+ // `delete` per Merge-Label retirement.
252
+ const canon = realCanon();
253
+ const creates = canon.labels.filter((label) => label.name.startsWith('disposition: ')).length;
254
+ expect(labelLines.length).toBe(canon.labels.length + creates + canon.merges.length);
255
+ // The shadow replaces the executable, so the stub on PATH never ran: no
256
+ // log file at all. This is the "-WhatIf touches nothing" invariant.
257
+ expect(fs.existsSync(stub.logPath)).toBe(false);
258
+ }
259
+ finally {
260
+ fs.rmSync(stub.dir, { recursive: true, force: true });
261
+ }
262
+ });
263
+ it('a normal run reaches gh for the whole canon — the -WhatIf shadow never leaks', () => {
264
+ const stub = makeGhStub();
265
+ try {
266
+ const run = runScript(['-Repo', 'example/stub'], stub.dir);
267
+ expect(run.error).toBeUndefined();
268
+ expect(run.status, run.stderr).toBe(0);
269
+ expect(run.stdout).not.toContain('[WhatIf]');
270
+ expect(fs.existsSync(stub.logPath), 'the stub gh was invoked').toBe(true);
271
+ const calls = fs
272
+ .readFileSync(stub.logPath, 'utf8')
273
+ .split(/\r?\n/)
274
+ .map((line) => line.trim())
275
+ .filter((line) => line.length > 0);
276
+ // The preflight ran (the unauthenticated silent no-op is now a hard stop).
277
+ expect(calls.some((call) => call.startsWith('auth status'))).toBe(true);
278
+ // Every canonical label was edited, by name, exactly once — the set and
279
+ // the count both derive from the real script (CodeRabbit on mmnto-ai/totem#2827).
280
+ const edited = calls
281
+ .map((call) => /^label edit "?(.+?)"? --color /.exec(call)?.[1])
282
+ .filter((name) => name !== undefined)
283
+ .sort();
284
+ const expectedEdited = realCanon()
285
+ .labels.map((label) => label.name)
286
+ .sort();
287
+ expect(edited).toEqual(expectedEdited);
288
+ const created = calls
289
+ .map((call) => /^label create "?(disposition: [a-z-]+)"?/.exec(call)?.[1])
290
+ .filter((name) => name !== undefined)
291
+ .sort();
292
+ const expectedCreated = realCanon()
293
+ .labels.map((label) => label.name)
294
+ .filter((name) => name.startsWith('disposition: '))
295
+ .sort();
296
+ expect(expectedCreated).toHaveLength(6);
297
+ expect(created).toEqual(expectedCreated);
298
+ }
299
+ finally {
300
+ fs.rmSync(stub.dir, { recursive: true, force: true });
301
+ }
302
+ });
303
+ it('a failed label edit fails the run — mutation errors never reach "sync complete"', () => {
304
+ // Authenticated but without write access, or an API failure: the literal
305
+ // lines suppress stderr by design, so only the exit CODE can carry the
306
+ // failure. The stub exits 1 on every `label edit`; the tally must name each
307
+ // canonical label and the script must exit non-zero (Greptile P1).
308
+ const stub = makeGhStub();
309
+ try {
310
+ const run = runScript(['-Repo', 'example/stub'], stub.dir, { TOTEM_GH_STUB_FAIL: '1' });
311
+ expect(run.error).toBeUndefined();
312
+ expect(run.status, run.stdout).toBe(1);
313
+ expect(run.stdout).toContain('label mutation(s) failed');
314
+ expect(run.stdout).not.toContain('sync complete');
315
+ const tallied = run.stdout
316
+ .split(/\r?\n/)
317
+ .filter((line) => line.includes('[Error] gh label edit')).length;
318
+ expect(tallied).toBe(realCanon().labels.length);
319
+ }
320
+ finally {
321
+ fs.rmSync(stub.dir, { recursive: true, force: true });
322
+ }
323
+ });
324
+ });
325
+ //# sourceMappingURL=sync-labels-forms.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-labels-forms.test.js","sourceRoot":"","sources":["../../src/commands/sync-labels-forms.test.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1429,8 +1429,8 @@ gateCmd
1429
1429
  .command('check')
1430
1430
  .description('Evaluate a gate predicate; emit a GateVerdict (allow|warn|deny) as JSON to stdout')
1431
1431
  .requiredOption('--event <type>', 'Gate event type (e.g. freeze-check)')
1432
- .requiredOption('--payload <json>', 'Gate-specific JSON payload')
1433
- .addHelpText('after', `\nExample:\n $ totem gate check --event freeze-check --payload '{"subsystem":"rule-compilation"}'\n`)
1432
+ .requiredOption('--payload <json>', 'Gate-specific JSON payload, or - to read it from stdin')
1433
+ .addHelpText('after', `\nExamples:\n $ totem gate check --event freeze-check --payload '{"subsystem":"rule-compilation"}'\n $ echo '{"tool":"Bash","command":"git status","platform":"win32"}' | totem gate check --event transport-shield --payload -\n`)
1434
1434
  .action(async (opts) => {
1435
1435
  try {
1436
1436
  const { gateCheckCommand } = await import('./commands/gate.js');