@mmnto/cli 1.5.3 → 1.5.5
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/commands/add-lesson.d.ts.map +1 -1
- package/dist/commands/add-lesson.js +12 -0
- package/dist/commands/add-lesson.js.map +1 -1
- package/dist/commands/add-lesson.test.d.ts +2 -0
- package/dist/commands/add-lesson.test.d.ts.map +1 -0
- package/dist/commands/add-lesson.test.js +63 -0
- package/dist/commands/add-lesson.test.js.map +1 -0
- package/dist/commands/add-secret.d.ts +5 -0
- package/dist/commands/add-secret.d.ts.map +1 -0
- package/dist/commands/add-secret.js +85 -0
- package/dist/commands/add-secret.js.map +1 -0
- package/dist/commands/add-secret.test.d.ts +2 -0
- package/dist/commands/add-secret.test.d.ts.map +1 -0
- package/dist/commands/add-secret.test.js +97 -0
- package/dist/commands/add-secret.test.js.map +1 -0
- package/dist/commands/doctor.d.ts +10 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +177 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +284 -2
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/extract.d.ts.map +1 -1
- package/dist/commands/extract.js +4 -1
- package/dist/commands/extract.js.map +1 -1
- package/dist/commands/extract.test.js +53 -0
- package/dist/commands/extract.test.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +16 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/ledger-analyzer.d.ts +24 -0
- package/dist/commands/ledger-analyzer.d.ts.map +1 -0
- package/dist/commands/ledger-analyzer.js +64 -0
- package/dist/commands/ledger-analyzer.js.map +1 -0
- package/dist/commands/ledger-analyzer.test.d.ts +2 -0
- package/dist/commands/ledger-analyzer.test.d.ts.map +1 -0
- package/dist/commands/ledger-analyzer.test.js +163 -0
- package/dist/commands/ledger-analyzer.test.js.map +1 -0
- package/dist/commands/list-secrets.d.ts +15 -0
- package/dist/commands/list-secrets.d.ts.map +1 -0
- package/dist/commands/list-secrets.js +104 -0
- package/dist/commands/list-secrets.js.map +1 -0
- package/dist/commands/list-secrets.test.d.ts +2 -0
- package/dist/commands/list-secrets.test.d.ts.map +1 -0
- package/dist/commands/list-secrets.test.js +85 -0
- package/dist/commands/list-secrets.test.js.map +1 -0
- package/dist/commands/remove-secret.d.ts +2 -0
- package/dist/commands/remove-secret.d.ts.map +1 -0
- package/dist/commands/remove-secret.js +53 -0
- package/dist/commands/remove-secret.js.map +1 -0
- package/dist/commands/remove-secret.test.d.ts +2 -0
- package/dist/commands/remove-secret.test.d.ts.map +1 -0
- package/dist/commands/remove-secret.test.js +85 -0
- package/dist/commands/remove-secret.test.js.map +1 -0
- package/dist/commands/review-learn-templates.d.ts +7 -0
- package/dist/commands/review-learn-templates.d.ts.map +1 -0
- package/dist/commands/review-learn-templates.js +36 -0
- package/dist/commands/review-learn-templates.js.map +1 -0
- package/dist/commands/review-learn-templates.test.d.ts +2 -0
- package/dist/commands/review-learn-templates.test.d.ts.map +1 -0
- package/dist/commands/review-learn-templates.test.js +29 -0
- package/dist/commands/review-learn-templates.test.js.map +1 -0
- package/dist/commands/review-learn.d.ts +13 -0
- package/dist/commands/review-learn.d.ts.map +1 -0
- package/dist/commands/review-learn.js +260 -0
- package/dist/commands/review-learn.js.map +1 -0
- package/dist/commands/review-learn.test.d.ts +2 -0
- package/dist/commands/review-learn.test.d.ts.map +1 -0
- package/dist/commands/review-learn.test.js +218 -0
- package/dist/commands/review-learn.test.js.map +1 -0
- package/dist/commands/rule-mutator.d.ts +17 -0
- package/dist/commands/rule-mutator.d.ts.map +1 -0
- package/dist/commands/rule-mutator.js +33 -0
- package/dist/commands/rule-mutator.js.map +1 -0
- package/dist/commands/rule-mutator.test.d.ts +2 -0
- package/dist/commands/rule-mutator.test.d.ts.map +1 -0
- package/dist/commands/rule-mutator.test.js +104 -0
- package/dist/commands/rule-mutator.test.js.map +1 -0
- package/dist/commands/run-compiled-rules.d.ts.map +1 -1
- package/dist/commands/run-compiled-rules.js +49 -5
- package/dist/commands/run-compiled-rules.js.map +1 -1
- package/dist/commands/run-compiled-rules.test.js +107 -1
- package/dist/commands/run-compiled-rules.test.js.map +1 -1
- package/dist/commands/shield-hints.d.ts +16 -2
- package/dist/commands/shield-hints.d.ts.map +1 -1
- package/dist/commands/shield-hints.js +35 -20
- package/dist/commands/shield-hints.js.map +1 -1
- package/dist/commands/shield-hints.test.js +70 -1
- package/dist/commands/shield-hints.test.js.map +1 -1
- package/dist/commands/shield.d.ts.map +1 -1
- package/dist/commands/shield.js +21 -2
- package/dist/commands/shield.js.map +1 -1
- package/dist/commands/triage-pr.d.ts +21 -0
- package/dist/commands/triage-pr.d.ts.map +1 -0
- package/dist/commands/triage-pr.js +231 -0
- package/dist/commands/triage-pr.js.map +1 -0
- package/dist/commands/triage-pr.test.d.ts +2 -0
- package/dist/commands/triage-pr.test.d.ts.map +1 -0
- package/dist/commands/triage-pr.test.js +163 -0
- package/dist/commands/triage-pr.test.js.map +1 -0
- package/dist/index.js +74 -4
- package/dist/index.js.map +1 -1
- package/dist/parsers/bot-review-parser.d.ts +48 -0
- package/dist/parsers/bot-review-parser.d.ts.map +1 -0
- package/dist/parsers/bot-review-parser.js +139 -0
- package/dist/parsers/bot-review-parser.js.map +1 -0
- package/dist/parsers/bot-review-parser.test.d.ts +2 -0
- package/dist/parsers/bot-review-parser.test.d.ts.map +1 -0
- package/dist/parsers/bot-review-parser.test.js +240 -0
- package/dist/parsers/bot-review-parser.test.js.map +1 -0
- package/dist/parsers/triage-dedup.d.ts +8 -0
- package/dist/parsers/triage-dedup.d.ts.map +1 -0
- package/dist/parsers/triage-dedup.js +134 -0
- package/dist/parsers/triage-dedup.js.map +1 -0
- package/dist/parsers/triage-dedup.test.d.ts +2 -0
- package/dist/parsers/triage-dedup.test.d.ts.map +1 -0
- package/dist/parsers/triage-dedup.test.js +209 -0
- package/dist/parsers/triage-dedup.test.js.map +1 -0
- package/dist/parsers/triage-severity-mapper.d.ts +9 -0
- package/dist/parsers/triage-severity-mapper.d.ts.map +1 -0
- package/dist/parsers/triage-severity-mapper.js +86 -0
- package/dist/parsers/triage-severity-mapper.js.map +1 -0
- package/dist/parsers/triage-severity-mapper.test.d.ts +2 -0
- package/dist/parsers/triage-severity-mapper.test.d.ts.map +1 -0
- package/dist/parsers/triage-severity-mapper.test.js +62 -0
- package/dist/parsers/triage-severity-mapper.test.js.map +1 -0
- package/dist/parsers/triage-types.d.ts +12 -0
- package/dist/parsers/triage-types.d.ts.map +1 -0
- package/dist/parsers/triage-types.js +2 -0
- package/dist/parsers/triage-types.js.map +1 -0
- package/dist/utils.d.ts +3 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { analyzeLedger, readLedgerBypassCounts } from './ledger-analyzer.js';
|
|
6
|
+
// ─── Helpers ────────────────────────────────────────────
|
|
7
|
+
function makeTmpDir() {
|
|
8
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'totem-ledger-analyzer-'));
|
|
9
|
+
}
|
|
10
|
+
function makeLedgerEvent(ruleId, type = 'suppress') {
|
|
11
|
+
return JSON.stringify({
|
|
12
|
+
timestamp: '2026-03-25T12:00:00.000Z',
|
|
13
|
+
type,
|
|
14
|
+
ruleId,
|
|
15
|
+
file: 'src/index.ts',
|
|
16
|
+
justification: type === 'override' ? 'Legacy code' : '',
|
|
17
|
+
source: 'lint',
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function writeLedger(totemDir, lines) {
|
|
21
|
+
const ledgerDir = path.join(totemDir, 'ledger');
|
|
22
|
+
fs.mkdirSync(ledgerDir, { recursive: true });
|
|
23
|
+
fs.writeFileSync(path.join(ledgerDir, 'events.ndjson'), lines.join('\n') + '\n', 'utf-8');
|
|
24
|
+
}
|
|
25
|
+
function writeMetrics(totemDir, rules) {
|
|
26
|
+
const cacheDir = path.join(totemDir, 'cache');
|
|
27
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
28
|
+
const metricsData = {
|
|
29
|
+
version: 1,
|
|
30
|
+
rules: Object.fromEntries(Object.entries(rules).map(([id, counts]) => [
|
|
31
|
+
id,
|
|
32
|
+
{
|
|
33
|
+
triggerCount: counts.triggerCount,
|
|
34
|
+
suppressCount: counts.suppressCount,
|
|
35
|
+
lastTriggeredAt: '2026-03-25T12:00:00.000Z',
|
|
36
|
+
lastSuppressedAt: null,
|
|
37
|
+
},
|
|
38
|
+
])),
|
|
39
|
+
};
|
|
40
|
+
fs.writeFileSync(path.join(cacheDir, 'rule-metrics.json'), JSON.stringify(metricsData, null, 2) + '\n', 'utf-8');
|
|
41
|
+
}
|
|
42
|
+
// ─── readLedgerBypassCounts ─────────────────────────────
|
|
43
|
+
describe('readLedgerBypassCounts', () => {
|
|
44
|
+
let tmpDir;
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
tmpDir = makeTmpDir();
|
|
47
|
+
});
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
50
|
+
});
|
|
51
|
+
it('returns empty map when ledger does not exist', async () => {
|
|
52
|
+
const counts = await readLedgerBypassCounts(tmpDir);
|
|
53
|
+
expect(counts.size).toBe(0);
|
|
54
|
+
});
|
|
55
|
+
it('counts bypass events per ruleId', async () => {
|
|
56
|
+
writeLedger(tmpDir, [
|
|
57
|
+
makeLedgerEvent('rule-a'),
|
|
58
|
+
makeLedgerEvent('rule-b'),
|
|
59
|
+
makeLedgerEvent('rule-a'),
|
|
60
|
+
makeLedgerEvent('rule-a', 'override'),
|
|
61
|
+
]);
|
|
62
|
+
const counts = await readLedgerBypassCounts(tmpDir);
|
|
63
|
+
expect(counts.get('rule-a')).toBe(3);
|
|
64
|
+
expect(counts.get('rule-b')).toBe(1);
|
|
65
|
+
});
|
|
66
|
+
it('skips malformed lines and warns', async () => {
|
|
67
|
+
const onWarn = vi.fn();
|
|
68
|
+
writeLedger(tmpDir, [makeLedgerEvent('rule-a'), '{{{invalid json', makeLedgerEvent('rule-b')]);
|
|
69
|
+
const counts = await readLedgerBypassCounts(tmpDir, onWarn);
|
|
70
|
+
expect(counts.get('rule-a')).toBe(1);
|
|
71
|
+
expect(counts.get('rule-b')).toBe(1);
|
|
72
|
+
expect(onWarn).toHaveBeenCalledWith('Skipping malformed ledger line');
|
|
73
|
+
});
|
|
74
|
+
it('skips events that fail schema validation', async () => {
|
|
75
|
+
writeLedger(tmpDir, [
|
|
76
|
+
makeLedgerEvent('rule-a'),
|
|
77
|
+
JSON.stringify({ type: 'suppress' }), // missing required fields
|
|
78
|
+
]);
|
|
79
|
+
const counts = await readLedgerBypassCounts(tmpDir);
|
|
80
|
+
expect(counts.get('rule-a')).toBe(1);
|
|
81
|
+
expect(counts.size).toBe(1);
|
|
82
|
+
});
|
|
83
|
+
it('skips blank lines', async () => {
|
|
84
|
+
const ledgerDir = path.join(tmpDir, 'ledger');
|
|
85
|
+
fs.mkdirSync(ledgerDir, { recursive: true });
|
|
86
|
+
fs.writeFileSync(path.join(ledgerDir, 'events.ndjson'), makeLedgerEvent('rule-a') + '\n\n\n' + makeLedgerEvent('rule-b') + '\n', 'utf-8');
|
|
87
|
+
const counts = await readLedgerBypassCounts(tmpDir);
|
|
88
|
+
expect(counts.get('rule-a')).toBe(1);
|
|
89
|
+
expect(counts.get('rule-b')).toBe(1);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
// ─── analyzeLedger ──────────────────────────────────────
|
|
93
|
+
describe('analyzeLedger', () => {
|
|
94
|
+
let tmpDir;
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
tmpDir = makeTmpDir();
|
|
97
|
+
});
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
100
|
+
});
|
|
101
|
+
it('returns empty map when no ledger or metrics exist', async () => {
|
|
102
|
+
const stats = await analyzeLedger(tmpDir);
|
|
103
|
+
expect(stats.size).toBe(0);
|
|
104
|
+
});
|
|
105
|
+
it('counts bypass events from ledger', async () => {
|
|
106
|
+
writeLedger(tmpDir, [
|
|
107
|
+
makeLedgerEvent('rule-a'),
|
|
108
|
+
makeLedgerEvent('rule-a'),
|
|
109
|
+
makeLedgerEvent('rule-b'),
|
|
110
|
+
]);
|
|
111
|
+
const stats = await analyzeLedger(tmpDir);
|
|
112
|
+
expect(stats.get('rule-a')?.bypassCount).toBe(2);
|
|
113
|
+
expect(stats.get('rule-b')?.bypassCount).toBe(1);
|
|
114
|
+
});
|
|
115
|
+
it('merges trigger counts from rule-metrics', async () => {
|
|
116
|
+
writeLedger(tmpDir, [makeLedgerEvent('rule-a')]);
|
|
117
|
+
writeMetrics(tmpDir, {
|
|
118
|
+
'rule-a': { triggerCount: 10, suppressCount: 1 },
|
|
119
|
+
'rule-c': { triggerCount: 5, suppressCount: 0 },
|
|
120
|
+
});
|
|
121
|
+
const stats = await analyzeLedger(tmpDir);
|
|
122
|
+
// rule-a: 10 triggers + 1 bypass from ledger
|
|
123
|
+
expect(stats.get('rule-a')?.triggerCount).toBe(10);
|
|
124
|
+
expect(stats.get('rule-a')?.bypassCount).toBe(1);
|
|
125
|
+
expect(stats.get('rule-a')?.totalEvents).toBe(11);
|
|
126
|
+
// rule-c: 5 triggers, 0 bypasses (not in ledger)
|
|
127
|
+
expect(stats.get('rule-c')?.triggerCount).toBe(5);
|
|
128
|
+
expect(stats.get('rule-c')?.bypassCount).toBe(0);
|
|
129
|
+
expect(stats.get('rule-c')?.totalEvents).toBe(5);
|
|
130
|
+
});
|
|
131
|
+
it('calculates correct bypass rate', async () => {
|
|
132
|
+
writeLedger(tmpDir, [makeLedgerEvent('rule-a'), makeLedgerEvent('rule-a')]);
|
|
133
|
+
writeMetrics(tmpDir, {
|
|
134
|
+
'rule-a': { triggerCount: 8, suppressCount: 2 },
|
|
135
|
+
});
|
|
136
|
+
const stats = await analyzeLedger(tmpDir);
|
|
137
|
+
const ruleA = stats.get('rule-a');
|
|
138
|
+
// 2 bypasses / (8 triggers + 2 bypasses) = 0.2
|
|
139
|
+
expect(ruleA.bypassRate).toBeCloseTo(0.2);
|
|
140
|
+
expect(ruleA.totalEvents).toBe(10);
|
|
141
|
+
});
|
|
142
|
+
it('handles div-by-zero: 0 events yields 0% rate', async () => {
|
|
143
|
+
// Rule exists in metrics with 0 triggers and 0 suppressions
|
|
144
|
+
writeMetrics(tmpDir, {
|
|
145
|
+
'rule-empty': { triggerCount: 0, suppressCount: 0 },
|
|
146
|
+
});
|
|
147
|
+
const stats = await analyzeLedger(tmpDir);
|
|
148
|
+
const ruleEmpty = stats.get('rule-empty');
|
|
149
|
+
expect(ruleEmpty.bypassRate).toBe(0);
|
|
150
|
+
expect(ruleEmpty.totalEvents).toBe(0);
|
|
151
|
+
});
|
|
152
|
+
it('handles missing ledger file gracefully', async () => {
|
|
153
|
+
writeMetrics(tmpDir, {
|
|
154
|
+
'rule-a': { triggerCount: 3, suppressCount: 0 },
|
|
155
|
+
});
|
|
156
|
+
const stats = await analyzeLedger(tmpDir);
|
|
157
|
+
const ruleA = stats.get('rule-a');
|
|
158
|
+
expect(ruleA.triggerCount).toBe(3);
|
|
159
|
+
expect(ruleA.bypassCount).toBe(0);
|
|
160
|
+
expect(ruleA.bypassRate).toBe(0);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
//# sourceMappingURL=ledger-analyzer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-analyzer.test.js","sourceRoot":"","sources":["../../src/commands/ledger-analyzer.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface SecretEntry {
|
|
2
|
+
index: number;
|
|
3
|
+
type: 'pattern' | 'literal';
|
|
4
|
+
value: string;
|
|
5
|
+
source: 'shared/yaml' | 'local/json';
|
|
6
|
+
}
|
|
7
|
+
/** Mask a literal secret value: show first 4 chars + `***`. */
|
|
8
|
+
export declare function maskLiteral(value: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Build an indexed list of all secrets from both sources.
|
|
11
|
+
* Does not print anything — used by both list and remove commands.
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildSecretEntries(cwd: string, totemDir?: string): Promise<SecretEntry[]>;
|
|
14
|
+
export declare function listSecretsCommand(cwd?: string, totemDir?: string): Promise<SecretEntry[]>;
|
|
15
|
+
//# sourceMappingURL=list-secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-secrets.d.ts","sourceRoot":"","sources":["../../src/commands/list-secrets.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,aAAa,GAAG,YAAY,CAAC;CACtC;AAID,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGjD;AA6CD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAW,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CA0BjG;AAID,wBAAsB,kBAAkB,CACtC,GAAG,SAAgB,EACnB,QAAQ,SAAW,GAClB,OAAO,CAAC,WAAW,EAAE,CAAC,CAsBxB"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { SecretsFileSchema } from '@mmnto/totem';
|
|
4
|
+
// ─── Constants ──────────────────────────────────────────
|
|
5
|
+
const TAG = 'ListSecrets';
|
|
6
|
+
const YAML_CONFIG_FILES = [
|
|
7
|
+
'totem.config.yaml',
|
|
8
|
+
'totem.config.yml',
|
|
9
|
+
'totem.yaml',
|
|
10
|
+
'totem.yml',
|
|
11
|
+
];
|
|
12
|
+
// ─── Helpers ────────────────────────────────────────────
|
|
13
|
+
/** Mask a literal secret value: show first 4 chars + `***`. */
|
|
14
|
+
export function maskLiteral(value) {
|
|
15
|
+
if (value.length <= 4)
|
|
16
|
+
return '****';
|
|
17
|
+
return value.slice(0, 4) + '***';
|
|
18
|
+
}
|
|
19
|
+
/** Read secrets from the first matching YAML config file. */
|
|
20
|
+
async function loadYamlSecrets(cwd) {
|
|
21
|
+
for (const file of YAML_CONFIG_FILES) {
|
|
22
|
+
const configPath = path.join(cwd, file);
|
|
23
|
+
if (!fs.existsSync(configPath))
|
|
24
|
+
continue;
|
|
25
|
+
try {
|
|
26
|
+
const { parse } = await import('yaml');
|
|
27
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
28
|
+
const parsed = parse(content);
|
|
29
|
+
if (parsed && Array.isArray(parsed.secrets)) {
|
|
30
|
+
return parsed.secrets.filter((s) => s && typeof s.type === 'string' && typeof s.value === 'string');
|
|
31
|
+
}
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
/** Read secrets from `.totem/secrets.json`. */
|
|
41
|
+
function loadJsonSecrets(cwd, totemDir) {
|
|
42
|
+
const jsonPath = path.join(cwd, totemDir, 'secrets.json');
|
|
43
|
+
if (!fs.existsSync(jsonPath))
|
|
44
|
+
return [];
|
|
45
|
+
try {
|
|
46
|
+
const content = fs.readFileSync(jsonPath, 'utf-8');
|
|
47
|
+
const parsed = JSON.parse(content);
|
|
48
|
+
const result = SecretsFileSchema.safeParse(parsed);
|
|
49
|
+
if (result.success) {
|
|
50
|
+
return result.data.secrets;
|
|
51
|
+
}
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// ─── Entry builder (shared with remove-secret) ─────────
|
|
59
|
+
/**
|
|
60
|
+
* Build an indexed list of all secrets from both sources.
|
|
61
|
+
* Does not print anything — used by both list and remove commands.
|
|
62
|
+
*/
|
|
63
|
+
export async function buildSecretEntries(cwd, totemDir = '.totem') {
|
|
64
|
+
const yamlSecrets = await loadYamlSecrets(cwd);
|
|
65
|
+
const jsonSecrets = loadJsonSecrets(cwd, totemDir);
|
|
66
|
+
const entries = [];
|
|
67
|
+
let idx = 1;
|
|
68
|
+
for (const secret of yamlSecrets) {
|
|
69
|
+
entries.push({
|
|
70
|
+
index: idx++,
|
|
71
|
+
type: secret.type,
|
|
72
|
+
value: secret.value,
|
|
73
|
+
source: 'shared/yaml',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
for (const secret of jsonSecrets) {
|
|
77
|
+
entries.push({
|
|
78
|
+
index: idx++,
|
|
79
|
+
type: secret.type,
|
|
80
|
+
value: secret.value,
|
|
81
|
+
source: 'local/json',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return entries;
|
|
85
|
+
}
|
|
86
|
+
// ─── Main ───────────────────────────────────────────────
|
|
87
|
+
export async function listSecretsCommand(cwd = process.cwd(), totemDir = '.totem') {
|
|
88
|
+
const { log } = await import('../ui.js');
|
|
89
|
+
const entries = await buildSecretEntries(cwd, totemDir);
|
|
90
|
+
if (entries.length === 0) {
|
|
91
|
+
log.info(TAG, 'No custom secrets configured.');
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
// Print header
|
|
95
|
+
log.info(TAG, `${entries.length} custom secret(s) configured:\n`);
|
|
96
|
+
// Print each entry
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
const displayValue = entry.type === 'literal' ? maskLiteral(entry.value) : entry.value;
|
|
99
|
+
console.error(` ${entry.index}. [${entry.type}] ${displayValue} (${entry.source})`);
|
|
100
|
+
}
|
|
101
|
+
console.error('');
|
|
102
|
+
return entries;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=list-secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-secrets.js","sourceRoot":"","sources":["../../src/commands/list-secrets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAGlC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,2DAA2D;AAE3D,MAAM,GAAG,GAAG,aAAa,CAAC;AAE1B,MAAM,iBAAiB,GAAG;IACxB,mBAAmB;IACnB,kBAAkB;IAClB,YAAY;IACZ,WAAW;CACH,CAAC;AAWX,2DAA2D;AAE3D,+DAA+D;AAC/D,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IACrC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC;AACnC,CAAC;AAED,6DAA6D;AAC7D,KAAK,UAAU,eAAe,CAAC,GAAW;IACxC,KAAK,MAAM,IAAI,IAAI,iBAAiB,EAAE,CAAC;QACrC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,SAAS;QAEzC,IAAI,CAAC;YACH,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;YACvC,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACrD,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAmC,CAAC;YAChE,IAAI,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC5C,OAAQ,MAAM,CAAC,OAA0B,CAAC,MAAM,CAC9C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CACtE,CAAC;YACJ,CAAC;YACD,OAAO,EAAE,CAAC;QACZ,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,+CAA+C;AAC/C,SAAS,eAAe,CAAC,GAAW,EAAE,QAAgB;IACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;IAC1D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAC;IAExC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAY,CAAC;QAC9C,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACnD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;QAC7B,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,0DAA0D;AAE1D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAW,EAAE,QAAQ,GAAG,QAAQ;IACvE,MAAM,WAAW,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,eAAe,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAEnD,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,IAAI,GAAG,GAAG,CAAC,CAAC;IAEZ,KAAK,MAAM,MAAM,IAAI,WAAW,EAAE,CAAC;QACjC,OAAO,CAAC,IAAI,CAAC;YACX,KAAK,EAAE,GAAG,EAAE;YACZ,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,MAAM,EAAE,aAAa;SACtB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,MAAM,MAAM,IAAI,WAAW,EAAE,CAAC;QACjC,OAAO,CAAC,IAAI,CAAC;YACX,KAAK,EAAE,GAAG,EAAE;YACZ,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,MAAM,EAAE,YAAY;SACrB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,2DAA2D;AAE3D,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,EACnB,QAAQ,GAAG,QAAQ;IAEnB,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;IAEzC,MAAM,OAAO,GAAG,MAAM,kBAAkB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAExD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,+BAA+B,CAAC,CAAC;QAC/C,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,eAAe;IACf,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,iCAAiC,CAAC,CAAC;IAElE,mBAAmB;IACnB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;QACvF,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,KAAK,MAAM,KAAK,CAAC,IAAI,KAAK,YAAY,MAAM,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACxF,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAElB,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-secrets.test.d.ts","sourceRoot":"","sources":["../../src/commands/list-secrets.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { listSecretsCommand, maskLiteral } from './list-secrets.js';
|
|
6
|
+
// ─── Helpers ────────────────────────────────────────────
|
|
7
|
+
let tmpDirs = [];
|
|
8
|
+
function makeTmpDir() {
|
|
9
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-list-secrets-'));
|
|
10
|
+
tmpDirs.push(dir);
|
|
11
|
+
return dir;
|
|
12
|
+
}
|
|
13
|
+
// ─── Tests ──────────────────────────────────────────────
|
|
14
|
+
describe('maskLiteral', () => {
|
|
15
|
+
it('masks values longer than 4 chars', () => {
|
|
16
|
+
expect(maskLiteral('my-secret-key')).toBe('my-s***');
|
|
17
|
+
});
|
|
18
|
+
it('masks values with exactly 4 chars', () => {
|
|
19
|
+
expect(maskLiteral('abcd')).toBe('****');
|
|
20
|
+
});
|
|
21
|
+
it('masks values shorter than 4 chars', () => {
|
|
22
|
+
expect(maskLiteral('abc')).toBe('****');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('listSecretsCommand', () => {
|
|
26
|
+
let stderrSpy;
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
stderrSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
29
|
+
});
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
stderrSpy.mockRestore();
|
|
32
|
+
for (const dir of tmpDirs) {
|
|
33
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
tmpDirs = [];
|
|
36
|
+
});
|
|
37
|
+
it('shows both shared and local secrets with source labels', async () => {
|
|
38
|
+
const cwd = makeTmpDir();
|
|
39
|
+
const totemDir = '.totem';
|
|
40
|
+
// Write YAML config with a shared secret
|
|
41
|
+
fs.writeFileSync(path.join(cwd, 'totem.yaml'), 'secrets:\n - type: pattern\n value: "ACME-[A-Z]{4}"\n - type: literal\n value: "shared-secret-1"\n', 'utf-8');
|
|
42
|
+
// Write local secrets.json
|
|
43
|
+
const totemDirPath = path.join(cwd, totemDir);
|
|
44
|
+
fs.mkdirSync(totemDirPath, { recursive: true });
|
|
45
|
+
fs.writeFileSync(path.join(totemDirPath, 'secrets.json'), JSON.stringify({
|
|
46
|
+
secrets: [{ type: 'literal', value: 'local-secret-1' }],
|
|
47
|
+
}), 'utf-8');
|
|
48
|
+
const entries = await listSecretsCommand(cwd, totemDir);
|
|
49
|
+
expect(entries).toHaveLength(3);
|
|
50
|
+
expect(entries[0].source).toBe('shared/yaml');
|
|
51
|
+
expect(entries[1].source).toBe('shared/yaml');
|
|
52
|
+
expect(entries[2].source).toBe('local/json');
|
|
53
|
+
const output = stderrSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
54
|
+
expect(output).toContain('shared/yaml');
|
|
55
|
+
expect(output).toContain('local/json');
|
|
56
|
+
expect(output).toContain('3 custom secret(s) configured');
|
|
57
|
+
});
|
|
58
|
+
it('shows message when no secrets configured', async () => {
|
|
59
|
+
const cwd = makeTmpDir();
|
|
60
|
+
const entries = await listSecretsCommand(cwd);
|
|
61
|
+
expect(entries).toHaveLength(0);
|
|
62
|
+
const output = stderrSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
63
|
+
expect(output).toContain('No custom secrets configured.');
|
|
64
|
+
});
|
|
65
|
+
it('masks literal values but shows pattern values', async () => {
|
|
66
|
+
const cwd = makeTmpDir();
|
|
67
|
+
const totemDir = '.totem';
|
|
68
|
+
const totemDirPath = path.join(cwd, totemDir);
|
|
69
|
+
fs.mkdirSync(totemDirPath, { recursive: true });
|
|
70
|
+
fs.writeFileSync(path.join(totemDirPath, 'secrets.json'), JSON.stringify({
|
|
71
|
+
secrets: [
|
|
72
|
+
{ type: 'literal', value: 'super-secret-value' },
|
|
73
|
+
{ type: 'pattern', value: 'ACME-[A-Z]{4}' },
|
|
74
|
+
],
|
|
75
|
+
}), 'utf-8');
|
|
76
|
+
await listSecretsCommand(cwd, totemDir);
|
|
77
|
+
const output = stderrSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
78
|
+
// Literal should be masked
|
|
79
|
+
expect(output).toContain('supe***');
|
|
80
|
+
expect(output).not.toContain('super-secret-value');
|
|
81
|
+
// Pattern should be shown in full
|
|
82
|
+
expect(output).toContain('ACME-[A-Z]{4}');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=list-secrets.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-secrets.test.js","sourceRoot":"","sources":["../../src/commands/list-secrets.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove-secret.d.ts","sourceRoot":"","sources":["../../src/commands/remove-secret.ts"],"names":[],"mappings":"AAYA,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,GAAG,SAAgB,EACnB,QAAQ,SAAW,GAClB,OAAO,CAAC,IAAI,CAAC,CA+Df"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
// ─── Constants ──────────────────────────────────────────
|
|
4
|
+
const TAG = 'RemoveSecret';
|
|
5
|
+
const SECRETS_REL_PATH = '.totem/secrets.json';
|
|
6
|
+
// ─── Main ───────────────────────────────────────────────
|
|
7
|
+
export async function removeSecretCommand(indexStr, cwd = process.cwd(), totemDir = '.totem') {
|
|
8
|
+
const { log } = await import('../ui.js');
|
|
9
|
+
const { buildSecretEntries } = await import('./list-secrets.js');
|
|
10
|
+
// 1. Build the full list to resolve index → source (silent, no output)
|
|
11
|
+
const entries = await buildSecretEntries(cwd, totemDir);
|
|
12
|
+
const index = parseInt(indexStr, 10);
|
|
13
|
+
if (isNaN(index) || index < 1 || index > entries.length) {
|
|
14
|
+
log.error('Totem Error', `Index ${indexStr} is out of range. Valid range: 1–${entries.length || 0}.`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const target = entries[index - 1];
|
|
18
|
+
// 2. Reject shared/yaml secrets
|
|
19
|
+
if (target.source === 'shared/yaml') {
|
|
20
|
+
log.error('Totem Error', 'Cannot remove shared secrets from CLI. Edit your totem.config.yaml directly.');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
// 3. Read secrets.json
|
|
24
|
+
const secretsPath = path.join(cwd, SECRETS_REL_PATH);
|
|
25
|
+
let data = { secrets: [] };
|
|
26
|
+
try {
|
|
27
|
+
const content = fs.readFileSync(secretsPath, 'utf-8');
|
|
28
|
+
data = JSON.parse(content);
|
|
29
|
+
if (!Array.isArray(data.secrets)) {
|
|
30
|
+
data.secrets = [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
log.error('Totem Error', `Failed to read ${SECRETS_REL_PATH}.`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
// 4. Compute the local index within secrets.json
|
|
38
|
+
// The entry's position among local/json entries maps to the JSON array.
|
|
39
|
+
// Count how many shared/yaml entries come before this entry.
|
|
40
|
+
const yamlCount = entries.filter((e) => e.source === 'shared/yaml').length;
|
|
41
|
+
const localIndex = index - 1 - yamlCount;
|
|
42
|
+
if (localIndex < 0 || localIndex >= data.secrets.length) {
|
|
43
|
+
log.error('Totem Error', `Index ${indexStr} is out of range for local secrets.`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
// 5. Remove
|
|
47
|
+
const [removed] = data.secrets.splice(localIndex, 1);
|
|
48
|
+
// 6. Write back
|
|
49
|
+
fs.writeFileSync(secretsPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
50
|
+
// 7. Confirm
|
|
51
|
+
log.success(TAG, `Removed ${removed.type} secret: "${removed.type === 'literal' ? removed.value.slice(0, 4) + '***' : removed.value}"`);
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=remove-secret.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove-secret.js","sourceRoot":"","sources":["../../src/commands/remove-secret.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove-secret.test.d.ts","sourceRoot":"","sources":["../../src/commands/remove-secret.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { removeSecretCommand } from './remove-secret.js';
|
|
6
|
+
// ─── Helpers ────────────────────────────────────────────
|
|
7
|
+
let tmpDirs = [];
|
|
8
|
+
function makeTmpDir() {
|
|
9
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-remove-secret-'));
|
|
10
|
+
tmpDirs.push(dir);
|
|
11
|
+
return dir;
|
|
12
|
+
}
|
|
13
|
+
function writeSecretsJson(cwd, totemDir, data) {
|
|
14
|
+
const dirPath = path.join(cwd, totemDir);
|
|
15
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
16
|
+
fs.writeFileSync(path.join(dirPath, 'secrets.json'), JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
17
|
+
}
|
|
18
|
+
function readSecretsJson(cwd, totemDir) {
|
|
19
|
+
const content = fs.readFileSync(path.join(cwd, totemDir, 'secrets.json'), 'utf-8');
|
|
20
|
+
return JSON.parse(content);
|
|
21
|
+
}
|
|
22
|
+
// ─── Tests ──────────────────────────────────────────────
|
|
23
|
+
describe('removeSecretCommand', () => {
|
|
24
|
+
let stderrSpy;
|
|
25
|
+
let exitSpy;
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
stderrSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
28
|
+
exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
29
|
+
throw new Error('process.exit called');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
stderrSpy.mockRestore();
|
|
34
|
+
exitSpy.mockRestore();
|
|
35
|
+
for (const dir of tmpDirs) {
|
|
36
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
tmpDirs = [];
|
|
39
|
+
});
|
|
40
|
+
it('removes entry at specified index', async () => {
|
|
41
|
+
const cwd = makeTmpDir();
|
|
42
|
+
const totemDir = '.totem';
|
|
43
|
+
writeSecretsJson(cwd, totemDir, {
|
|
44
|
+
secrets: [
|
|
45
|
+
{ type: 'literal', value: 'first-secret' },
|
|
46
|
+
{ type: 'pattern', value: 'PATTERN-\\d+' },
|
|
47
|
+
{ type: 'literal', value: 'third-secret' },
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
// Remove the second entry (index 2, which is the second local/json secret)
|
|
51
|
+
await removeSecretCommand('2', cwd, totemDir);
|
|
52
|
+
const updated = readSecretsJson(cwd, totemDir);
|
|
53
|
+
expect(updated.secrets).toHaveLength(2);
|
|
54
|
+
expect(updated.secrets[0].value).toBe('first-secret');
|
|
55
|
+
expect(updated.secrets[1].value).toBe('third-secret');
|
|
56
|
+
const output = stderrSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
57
|
+
expect(output).toContain('Removed');
|
|
58
|
+
});
|
|
59
|
+
it('rejects out-of-range index', async () => {
|
|
60
|
+
const cwd = makeTmpDir();
|
|
61
|
+
const totemDir = '.totem';
|
|
62
|
+
writeSecretsJson(cwd, totemDir, {
|
|
63
|
+
secrets: [{ type: 'literal', value: 'only-secret' }],
|
|
64
|
+
});
|
|
65
|
+
await expect(removeSecretCommand('5', cwd, totemDir)).rejects.toThrow('process.exit called');
|
|
66
|
+
const output = stderrSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
67
|
+
expect(output).toContain('out of range');
|
|
68
|
+
});
|
|
69
|
+
it('rejects removal of shared yaml secrets', async () => {
|
|
70
|
+
const cwd = makeTmpDir();
|
|
71
|
+
const totemDir = '.totem';
|
|
72
|
+
// Write a YAML config with a shared secret
|
|
73
|
+
fs.writeFileSync(path.join(cwd, 'totem.yaml'), 'secrets:\n - type: literal\n value: "yaml-secret-val"\n', 'utf-8');
|
|
74
|
+
// Write local secrets.json
|
|
75
|
+
writeSecretsJson(cwd, totemDir, {
|
|
76
|
+
secrets: [{ type: 'literal', value: 'local-secret' }],
|
|
77
|
+
});
|
|
78
|
+
// Index 1 should be the yaml secret
|
|
79
|
+
await expect(removeSecretCommand('1', cwd, totemDir)).rejects.toThrow('process.exit called');
|
|
80
|
+
const output = stderrSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
81
|
+
expect(output).toContain('Cannot remove shared secrets from CLI');
|
|
82
|
+
expect(output).toContain('totem.config.yaml');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=remove-secret.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove-secret.test.js","sourceRoot":"","sources":["../../src/commands/remove-secret.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Maximum existing lessons to include for dedup context */
|
|
2
|
+
export declare const MAX_EXISTING_LESSONS = 10;
|
|
3
|
+
/** Maximum assembled prompt size in characters */
|
|
4
|
+
export declare const MAX_PROMPT_CHARS = 100000;
|
|
5
|
+
/** System prompt for review-learn — instructs LLM to extract lessons from resolved bot findings */
|
|
6
|
+
export declare const REVIEW_LEARN_SYSTEM_PROMPT = "You are Totem's lesson extractor for bot code review findings.\n\nYou receive a set of code review findings from automated bots (CodeRabbit, Gemini Code Assist) that were RESOLVED (the developer accepted and fixed them). Your job is to extract reusable architectural lessons from these findings.\n\nRULES:\n1. Only extract lessons that represent reusable patterns \u2014 NOT one-off fixes.\n2. Each lesson must be actionable: what the symptom is, what the fix is, and why it matters.\n3. Every lesson MUST include lifecycle: nursery in its metadata \u2014 these are unproven until validated.\n4. Deduplicate against the provided existing lessons. Do NOT repeat known patterns.\n5. Focus on architectural and security findings. Skip pure style/formatting nits unless they represent a real pattern.\n6. If no findings warrant a lesson, return an empty array.\n\nOUTPUT FORMAT:\nReturn a JSON array of lesson objects. Each lesson has:\n- \"tags\": string[] \u2014 relevant tags (e.g., [\"security\", \"typescript\", \"architecture\"])\n- \"text\": string \u2014 the lesson body. Start with the symptom/pattern, then the fix.\n- \"lifecycle\": \"nursery\" \u2014 REQUIRED, always \"nursery\"\n\nExample:\n[\n {\n \"tags\": [\"security\", \"shell\"],\n \"text\": \"Using execSync with string interpolation for shell commands creates injection risk. Use spawnSync with an args array to pass arguments safely without shell interpretation.\",\n \"lifecycle\": \"nursery\"\n }\n]\n\nIf no lessons should be extracted, return: []";
|
|
7
|
+
//# sourceMappingURL=review-learn-templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-learn-templates.d.ts","sourceRoot":"","sources":["../../src/commands/review-learn-templates.ts"],"names":[],"mappings":"AAEA,4DAA4D;AAC5D,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,kDAAkD;AAClD,eAAO,MAAM,gBAAgB,SAAU,CAAC;AAIxC,mGAAmG;AACnG,eAAO,MAAM,0BAA0B,+/CA2BO,CAAC"}
|