@mmnto/cli 1.120.0 → 1.122.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/dist/artifact-vocabulary.d.ts +23 -0
- package/dist/artifact-vocabulary.d.ts.map +1 -0
- package/dist/artifact-vocabulary.js +23 -0
- package/dist/artifact-vocabulary.js.map +1 -0
- package/dist/commands/config-drift.test.js +26 -2
- package/dist/commands/config-drift.test.js.map +1 -1
- package/dist/commands/doctor-parity.d.ts.map +1 -1
- package/dist/commands/doctor-parity.js +21 -6
- package/dist/commands/doctor-parity.js.map +1 -1
- package/dist/commands/doctor-parity.test.js +14 -7
- package/dist/commands/doctor-parity.test.js.map +1 -1
- package/dist/commands/doctor.d.ts +21 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +167 -9
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +234 -6
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/ecl-gc.d.ts.map +1 -1
- package/dist/commands/ecl-gc.js +38 -5
- package/dist/commands/ecl-gc.js.map +1 -1
- package/dist/commands/ecl-gc.test.js +71 -0
- package/dist/commands/ecl-gc.test.js.map +1 -1
- package/dist/commands/eject-totemdir.test.d.ts +8 -0
- package/dist/commands/eject-totemdir.test.d.ts.map +1 -0
- package/dist/commands/eject-totemdir.test.js +101 -0
- package/dist/commands/eject-totemdir.test.js.map +1 -0
- package/dist/commands/eject.d.ts +30 -5
- package/dist/commands/eject.d.ts.map +1 -1
- package/dist/commands/eject.js +145 -31
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/eject.test.js +3 -3
- package/dist/commands/eject.test.js.map +1 -1
- package/dist/commands/hook-totemdir-render.test.d.ts +23 -0
- package/dist/commands/hook-totemdir-render.test.d.ts.map +1 -0
- package/dist/commands/hook-totemdir-render.test.js +288 -0
- package/dist/commands/hook-totemdir-render.test.js.map +1 -0
- package/dist/commands/init-templates.d.ts +4 -4
- package/dist/commands/init-templates.d.ts.map +1 -1
- package/dist/commands/init-templates.js +3 -3
- package/dist/commands/install-hooks-exit-contract.test.js +6 -6
- package/dist/commands/install-hooks-exit-contract.test.js.map +1 -1
- package/dist/commands/install-hooks.d.ts +162 -12
- package/dist/commands/install-hooks.d.ts.map +1 -1
- package/dist/commands/install-hooks.js +521 -88
- package/dist/commands/install-hooks.js.map +1 -1
- package/dist/commands/install-hooks.test.js +922 -147
- package/dist/commands/install-hooks.test.js.map +1 -1
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/link.js +71 -16
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/link.test.d.ts +9 -0
- package/dist/commands/link.test.d.ts.map +1 -0
- package/dist/commands/link.test.js +92 -0
- package/dist/commands/link.test.js.map +1 -0
- package/dist/commands/mail.d.ts +48 -2
- package/dist/commands/mail.d.ts.map +1 -1
- package/dist/commands/mail.js +76 -2
- package/dist/commands/mail.js.map +1 -1
- package/dist/commands/mail.test.js +177 -0
- package/dist/commands/mail.test.js.map +1 -1
- package/dist/commands/pre-push-gate-matrix.test.js +9 -2
- package/dist/commands/pre-push-gate-matrix.test.js.map +1 -1
- package/dist/commands/shield.js +1 -1
- package/dist/commands/shield.js.map +1 -1
- package/dist/commands/spec-cli-wiring.test.d.ts +19 -0
- package/dist/commands/spec-cli-wiring.test.d.ts.map +1 -0
- package/dist/commands/spec-cli-wiring.test.js +90 -0
- package/dist/commands/spec-cli-wiring.test.js.map +1 -0
- package/dist/commands/spec-templates.d.ts +18 -0
- package/dist/commands/spec-templates.d.ts.map +1 -1
- package/dist/commands/spec-templates.js +21 -0
- package/dist/commands/spec-templates.js.map +1 -1
- package/dist/commands/spec.d.ts +168 -1
- package/dist/commands/spec.d.ts.map +1 -1
- package/dist/commands/spec.js +448 -7
- package/dist/commands/spec.js.map +1 -1
- package/dist/commands/spec.test.js +903 -20
- package/dist/commands/spec.test.js.map +1 -1
- package/dist/commands/tools-hook-parity.test.js +7 -3
- package/dist/commands/tools-hook-parity.test.js.map +1 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/services/run-artifacts.d.ts +12 -1
- package/dist/services/run-artifacts.d.ts.map +1 -1
- package/dist/services/run-artifacts.js +48 -3
- package/dist/services/run-artifacts.js.map +1 -1
- package/dist/services/run-artifacts.test.js +97 -1
- package/dist/services/run-artifacts.test.js.map +1 -1
- package/dist/utils.d.ts +22 -3
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +4 -0
- package/dist/utils.js.map +1 -1
- package/dist/utils.test.js +65 -1
- package/dist/utils.test.js.map +1 -1
- package/package.json +2 -2
|
@@ -716,6 +716,11 @@ describe('pollMail — lifecycle-aware broadcast denominators (mmnto-ai/totem#25
|
|
|
716
716
|
// Additive field (fold): a markerless tree emits zero notices — the
|
|
717
717
|
// channel's existence is the only delta vs the pre-#2511 result shape.
|
|
718
718
|
notices: [],
|
|
719
|
+
// Additive REQUIRED field (mmnto-ai/totem#2685): the `to: cohort` exhibit
|
|
720
|
+
// above lives in a FOREIGN repo's outbox (this poll's repo root is
|
|
721
|
+
// `<workspace>/totem`), so it stays a roster WARNING and this channel is
|
|
722
|
+
// empty — the ownership test, asserted inside the whole-shape snapshot.
|
|
723
|
+
senderFaults: [],
|
|
719
724
|
});
|
|
720
725
|
});
|
|
721
726
|
});
|
|
@@ -875,6 +880,157 @@ describe('pollMail — outbox roster-validation sensor (mmnto-ai/totem#2335)', (
|
|
|
875
880
|
]);
|
|
876
881
|
expect(poll().warnings.filter((w) => w.startsWith(UNRESOLVABLE_PREFIX))).toHaveLength(2);
|
|
877
882
|
});
|
|
883
|
+
// ─── Sender-fault floor (mmnto-ai/totem#2685) ─────────
|
|
884
|
+
//
|
|
885
|
+
// The same detector, split by OWNERSHIP: a dispatch in an outbox THIS repo
|
|
886
|
+
// hosts for a seat in the poll's self-set is the polling seat's own problem
|
|
887
|
+
// (structured fault + `Error:` line + exit 4 + a red ecl-gc gate), while a
|
|
888
|
+
// foreign repo's undeliverable dispatch keeps the exit-neutral warning above.
|
|
889
|
+
// Live specimen: the 2026-08-26 blind round, `to: lc-agy, lc-codex,
|
|
890
|
+
// lc-gemini, lc-kimi` — a comma list parses as ONE scalar recipient matching
|
|
891
|
+
// no seat, so the sender's poll exited 0 with a clean inbox verdict beside a
|
|
892
|
+
// round that never left.
|
|
893
|
+
describe('own-hosted sender faults (mmnto-ai/totem#2685)', () => {
|
|
894
|
+
const COMMA_TO = 'lc-agy, lc-codex, lc-gemini, lc-kimi';
|
|
895
|
+
const COMMA_HINT = 'a comma list is not a recipient — ADR-098 `to:` is single-valued; send one directed dispatch per recipient';
|
|
896
|
+
const FAULT_FILE = '2026-08-26T0900Z-lc-agy-blind-round.md';
|
|
897
|
+
/** Write a dispatch into THIS repo's own outbox (`<workspace>/totem/…`). */
|
|
898
|
+
function writeOwnOutbox(seat, files) {
|
|
899
|
+
writeOutbox('totem', seat, files);
|
|
900
|
+
}
|
|
901
|
+
it('an own-hosted unresolvable `to:` yields ONE structured fault and NO roster warning for that file', () => {
|
|
902
|
+
writeOwnOutbox('totem-claude', [{ name: FAULT_FILE, to: COMMA_TO, subject: 'r2 blind' }]);
|
|
903
|
+
const result = poll({ env: SELF_CLAUDE });
|
|
904
|
+
expect(result.senderFaults).toEqual([
|
|
905
|
+
{
|
|
906
|
+
kind: 'undeliverable-to',
|
|
907
|
+
repo: 'totem',
|
|
908
|
+
agent: 'totem-claude',
|
|
909
|
+
file: FAULT_FILE,
|
|
910
|
+
to: COMMA_TO,
|
|
911
|
+
hint: COMMA_HINT,
|
|
912
|
+
},
|
|
913
|
+
]);
|
|
914
|
+
// NON-VACUITY: the channels are disjoint per file — routing the fault to
|
|
915
|
+
// `warnings` too would pin the verdict to INCOMPLETE on a complete scan.
|
|
916
|
+
expect(result.warnings.filter((w) => w.startsWith(UNRESOLVABLE_PREFIX))).toEqual([]);
|
|
917
|
+
expect(result.warnings).toEqual([]);
|
|
918
|
+
expect(resolveMailExitCode(result)).toBe(4);
|
|
919
|
+
});
|
|
920
|
+
it('`hint` is non-null iff the `to:` carries a comma', () => {
|
|
921
|
+
writeOwnOutbox('totem-claude', [
|
|
922
|
+
{ name: '2026-08-26T0901Z-typo.md', to: 'totem-claud', subject: 'fat-fingered' },
|
|
923
|
+
]);
|
|
924
|
+
const result = poll({ env: SELF_CLAUDE });
|
|
925
|
+
expect(result.senderFaults).toHaveLength(1);
|
|
926
|
+
expect(result.senderFaults[0].to).toBe('totem-claud');
|
|
927
|
+
expect(result.senderFaults[0].hint).toBeNull();
|
|
928
|
+
});
|
|
929
|
+
it('renders an `Error:` line that never masks the inbox verdict', () => {
|
|
930
|
+
writeOwnOutbox('totem-claude', [{ name: FAULT_FILE, to: COMMA_TO, subject: 'r2 blind' }]);
|
|
931
|
+
const text = formatTextResult(poll({ env: SELF_CLAUDE })).split('\n');
|
|
932
|
+
const errorIdx = text.findIndex((l) => l.startsWith('Error: '));
|
|
933
|
+
const verdictIdx = text.findIndex((l) => l.startsWith('No unread mail'));
|
|
934
|
+
expect(text[errorIdx]).toBe(`Error: undeliverable dispatch in YOUR outbox: totem/totem-claude/${FAULT_FILE} — to: "${COMMA_TO}" matches no roster agent; invisible to every seat-scoped poll. Fix the to: (one recipient per dispatch, or broadcast) and re-poll. ${COMMA_HINT}`);
|
|
935
|
+
// The verdict STILL renders, after the fault line and unqualified: a
|
|
936
|
+
// sender fault is not a scan-integrity anomaly, so the #2516 INCOMPLETE
|
|
937
|
+
// discriminator (keyed on `warnings`) must stay silent.
|
|
938
|
+
expect(verdictIdx).toBeGreaterThan(errorIdx);
|
|
939
|
+
expect(text.join('\n')).not.toContain('INCOMPLETE');
|
|
940
|
+
});
|
|
941
|
+
it('--json carries the structured fault object (additive key)', async () => {
|
|
942
|
+
writeOwnOutbox('totem-claude', [{ name: FAULT_FILE, to: COMMA_TO, subject: 'r2 blind' }]);
|
|
943
|
+
const writes = [];
|
|
944
|
+
const spy = vi
|
|
945
|
+
.spyOn(process.stdout, 'write')
|
|
946
|
+
.mockImplementation((chunk) => {
|
|
947
|
+
writes.push(typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf-8'));
|
|
948
|
+
return true;
|
|
949
|
+
});
|
|
950
|
+
let exitCode;
|
|
951
|
+
try {
|
|
952
|
+
({ exitCode } = await mailCommand({
|
|
953
|
+
json: true,
|
|
954
|
+
repoRoot: selfRepoRoot(),
|
|
955
|
+
workspace,
|
|
956
|
+
env: SELF_CLAUDE,
|
|
957
|
+
}));
|
|
958
|
+
}
|
|
959
|
+
finally {
|
|
960
|
+
spy.mockRestore();
|
|
961
|
+
}
|
|
962
|
+
const parsed = JSON.parse(writes[0]);
|
|
963
|
+
expect(parsed.senderFaults).toEqual([
|
|
964
|
+
{
|
|
965
|
+
kind: 'undeliverable-to',
|
|
966
|
+
repo: 'totem',
|
|
967
|
+
agent: 'totem-claude',
|
|
968
|
+
file: FAULT_FILE,
|
|
969
|
+
to: COMMA_TO,
|
|
970
|
+
hint: COMMA_HINT,
|
|
971
|
+
},
|
|
972
|
+
]);
|
|
973
|
+
expect(exitCode).toBe(4);
|
|
974
|
+
});
|
|
975
|
+
it('a same-named seat dir in a FOREIGN repo stays a warning naming that repo — never a fault', () => {
|
|
976
|
+
// PATH-keyed ownership, not label-keyed: `totem-claude` is in this poll's
|
|
977
|
+
// self-set, but the outbox lives in another workspace repo (residue dir,
|
|
978
|
+
// second checkout, fan-out copy). NON-VACUITY: a label-only test would
|
|
979
|
+
// fault here and hand this seat an exit-4 it cannot fix.
|
|
980
|
+
writeOutbox('liquid-city', 'totem-claude', [
|
|
981
|
+
{ name: FAULT_FILE, to: COMMA_TO, subject: 'r2 blind' },
|
|
982
|
+
]);
|
|
983
|
+
const result = poll({ env: SELF_CLAUDE });
|
|
984
|
+
expect(result.senderFaults).toEqual([]);
|
|
985
|
+
const rosterWarnings = result.warnings.filter((w) => w.startsWith(UNRESOLVABLE_PREFIX));
|
|
986
|
+
expect(rosterWarnings).toHaveLength(1);
|
|
987
|
+
expect(rosterWarnings[0]).toContain(`liquid-city/totem-claude/${FAULT_FILE}`);
|
|
988
|
+
expect(resolveMailExitCode(result)).toBe(0);
|
|
989
|
+
});
|
|
990
|
+
it('an identity-gated poll says "an outbox this repo hosts" and stays exit 2 (NOT-DERIVED precedence)', () => {
|
|
991
|
+
// No explicit identity: `totem` resolves two seats via dirs∪map, so the
|
|
992
|
+
// self-set is a UNION — the poll cannot claim the outbox is YOURS.
|
|
993
|
+
writeOwnOutbox('totem-claude', [{ name: FAULT_FILE, to: COMMA_TO, subject: 'r2 blind' }]);
|
|
994
|
+
const result = poll();
|
|
995
|
+
expect(result.seatGate).toBeDefined();
|
|
996
|
+
expect(result.senderFaults).toHaveLength(1);
|
|
997
|
+
const text = formatTextResult(result);
|
|
998
|
+
expect(text).toContain('Error: undeliverable dispatch in an outbox this repo hosts:');
|
|
999
|
+
expect(text).not.toContain('YOUR outbox');
|
|
1000
|
+
// Exit 2: nothing in the outbox is worth telling this caller to fix until
|
|
1001
|
+
// identity resolves — and exit 4 here would be read as "fix the to:".
|
|
1002
|
+
expect(resolveMailExitCode(result)).toBe(2);
|
|
1003
|
+
});
|
|
1004
|
+
it('--all-seats + a fault in a PEER host seat outbox is exit 4 (dashboard semantics)', () => {
|
|
1005
|
+
// The union view is asked for BY NAME, so every seat this repo hosts is
|
|
1006
|
+
// "own" — the dashboard is the surface that should go red.
|
|
1007
|
+
writeOwnOutbox('totem-gemini', [{ name: FAULT_FILE, to: COMMA_TO, subject: 'r2 blind' }]);
|
|
1008
|
+
const result = poll({ allSeats: true });
|
|
1009
|
+
expect(result.seatGate).toBeUndefined();
|
|
1010
|
+
expect(result.senderFaults.map((f) => f.agent)).toEqual(['totem-gemini']);
|
|
1011
|
+
expect(formatTextResult(result)).toContain('an outbox this repo hosts');
|
|
1012
|
+
expect(resolveMailExitCode(result)).toBe(4);
|
|
1013
|
+
});
|
|
1014
|
+
it('`to: broadcast` and an env self-id outside the cohort map never fault', () => {
|
|
1015
|
+
writeOwnOutbox('totem-claude', [
|
|
1016
|
+
{ name: '2026-08-26T0902Z-broadcast-round.md', to: 'broadcast', subject: 'cohort-wide' },
|
|
1017
|
+
{ name: '2026-08-26T0903Z-custom-id-note.md', to: 'custom-id', subject: 'env self' },
|
|
1018
|
+
]);
|
|
1019
|
+
const result = poll({ env: { TOTEM_SELF_AGENT: 'custom-id' } });
|
|
1020
|
+
expect(result.senderFaults).toEqual([]);
|
|
1021
|
+
expect(result.warnings.filter((w) => w.startsWith(UNRESOLVABLE_PREFIX))).toEqual([]);
|
|
1022
|
+
expect(resolveMailExitCode(result)).toBe(0);
|
|
1023
|
+
});
|
|
1024
|
+
it('a clean poll carries an empty channel and stays exit 0', () => {
|
|
1025
|
+
writeOwnOutbox('totem-claude', [
|
|
1026
|
+
{ name: '2026-08-26T0904Z-lc-claude-ok.md', to: 'lc-claude', subject: 'deliverable' },
|
|
1027
|
+
]);
|
|
1028
|
+
const result = poll({ env: SELF_CLAUDE });
|
|
1029
|
+
expect(result.senderFaults).toEqual([]);
|
|
1030
|
+
expect(resolveMailExitCode(result)).toBe(0);
|
|
1031
|
+
expect(formatTextResult(result)).not.toContain('Error:');
|
|
1032
|
+
});
|
|
1033
|
+
});
|
|
878
1034
|
});
|
|
879
1035
|
// ─── Sort + metadata ────────────────────────────────────
|
|
880
1036
|
describe('pollMail — sort + metadata', () => {
|
|
@@ -1607,12 +1763,33 @@ describe('resolveMailExitCode (unit)', () => {
|
|
|
1607
1763
|
workspace: '/w',
|
|
1608
1764
|
warnings: [],
|
|
1609
1765
|
notices: [],
|
|
1766
|
+
senderFaults: [],
|
|
1610
1767
|
};
|
|
1611
1768
|
}
|
|
1612
1769
|
it('is 2 when self is unresolved (source none), 0 when resolved', () => {
|
|
1613
1770
|
expect(resolveMailExitCode(result({ agents: [], source: 'none' }))).toBe(2);
|
|
1614
1771
|
expect(resolveMailExitCode(result({ agents: ['totem-claude'], source: 'map' }))).toBe(0);
|
|
1615
1772
|
});
|
|
1773
|
+
it('is 4 on a sender fault, and NOT-DERIVED keeps precedence over it (mmnto-ai/totem#2685)', () => {
|
|
1774
|
+
const fault = {
|
|
1775
|
+
kind: 'undeliverable-to',
|
|
1776
|
+
repo: 'totem',
|
|
1777
|
+
agent: 'totem-claude',
|
|
1778
|
+
file: '2026-08-26T0900Z-blind-round.md',
|
|
1779
|
+
to: 'lc-agy, lc-codex',
|
|
1780
|
+
hint: 'a comma list is not a recipient — ADR-098 `to:` is single-valued; send one directed dispatch per recipient',
|
|
1781
|
+
};
|
|
1782
|
+
const resolved = {
|
|
1783
|
+
...result({ agents: ['totem-claude'], source: 'env' }),
|
|
1784
|
+
senderFaults: [fault],
|
|
1785
|
+
};
|
|
1786
|
+
expect(resolveMailExitCode(resolved)).toBe(4);
|
|
1787
|
+
// NOT-DERIVED (unresolved self) outranks the fault arm: until identity
|
|
1788
|
+
// resolves there is nothing in the outbox the caller can be told to fix.
|
|
1789
|
+
expect(resolveMailExitCode({ ...resolved, selfAgents: { agents: [], source: 'none' } })).toBe(2);
|
|
1790
|
+
// …and so does the identity gate.
|
|
1791
|
+
expect(resolveMailExitCode({ ...resolved, seatGate: { withheldDirected: 1 } })).toBe(2);
|
|
1792
|
+
});
|
|
1616
1793
|
});
|
|
1617
1794
|
describe('mailCommand — exit contract + NOT-DERIVED verdict (mmnto-ai/totem#2312)', () => {
|
|
1618
1795
|
function unknownRepo() {
|