@codyswann/lisa 2.142.2 → 2.143.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/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/commands/repair-intake.md +2 -2
- package/plugins/lisa/skills/repair-intake/SKILL.md +57 -5
- package/plugins/lisa-agy/commands/repair-intake.md +2 -2
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/repair-intake/SKILL.md +57 -5
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/commands/repair-intake.md +2 -2
- package/plugins/lisa-copilot/skills/repair-intake/SKILL.md +57 -5
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/commands/repair-intake.md +2 -2
- package/plugins/lisa-cursor/skills/repair-intake/SKILL.md +57 -5
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/scripts/wiki-safety.mjs +199 -0
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/scripts/wiki-safety.mjs +199 -0
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/scripts/wiki-safety.mjs +199 -0
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/scripts/wiki-safety.mjs +199 -0
- package/plugins/src/base/commands/repair-intake.md +2 -2
- package/plugins/src/base/skills/repair-intake/SKILL.md +57 -5
- package/plugins/src/wiki/scripts/wiki-safety.mjs +199 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic wiki source safety helpers.
|
|
4
|
+
*
|
|
5
|
+
* This module intentionally uses only Node built-ins and pure string scanning so
|
|
6
|
+
* downstream wiki connectors can run the same redaction pass before persisting
|
|
7
|
+
* reader-safe source notes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const PLACEHOLDERS = {
|
|
11
|
+
ssn: "[REDACTED:SSN]",
|
|
12
|
+
credit_card: "[REDACTED:CREDIT_CARD]",
|
|
13
|
+
private_key: "[REDACTED:PRIVATE_KEY]",
|
|
14
|
+
password: "[REDACTED:PASSWORD]",
|
|
15
|
+
api_key: "[REDACTED:API_KEY]",
|
|
16
|
+
oauth_token: "[REDACTED:OAUTH_TOKEN]",
|
|
17
|
+
bank_account: "[REDACTED:BANK_ACCOUNT]",
|
|
18
|
+
routing_number: "[REDACTED:ROUTING_NUMBER]",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const PATTERNS = [
|
|
22
|
+
{
|
|
23
|
+
entityType: "private_key",
|
|
24
|
+
confidence: "high",
|
|
25
|
+
re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/g,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
entityType: "ssn",
|
|
29
|
+
confidence: "high",
|
|
30
|
+
re: /\b(?!000|666|9\d\d)\d{3}-(?!00)\d{2}-(?!0000)\d{4}\b/g,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
entityType: "password",
|
|
34
|
+
confidence: "high",
|
|
35
|
+
re: /\b(?:password|passwd|pwd)\s*[:=]\s*(['"]?)([^\s'",;]{8,})\1/gi,
|
|
36
|
+
valueGroup: 2,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
entityType: "api_key",
|
|
40
|
+
confidence: "medium",
|
|
41
|
+
re: /\b(?:api[_-]?key|access[_-]?key|secret[_-]?key|client[_-]?secret)\s*[:=]\s*(['"]?)([A-Za-z0-9._-]{20,})\1/gi,
|
|
42
|
+
valueGroup: 2,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
entityType: "oauth_token",
|
|
46
|
+
confidence: "high",
|
|
47
|
+
re: /\b(?:oauth[_-]?token|refresh[_-]?token|access[_-]?token|bearer)\s*[:= ]\s*(['"]?)([A-Za-z0-9._-]{24,})\1/gi,
|
|
48
|
+
valueGroup: 2,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
entityType: "routing_number",
|
|
52
|
+
confidence: "medium",
|
|
53
|
+
re: /\b(?:routing|routing_number|aba)\s*(?:number|no\.?)?\s*[:#=]?\s*(\d{9})\b/gi,
|
|
54
|
+
valueGroup: 1,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
entityType: "bank_account",
|
|
58
|
+
confidence: "medium",
|
|
59
|
+
re: /\b(?:bank\s+)?(?:account|acct)\s*(?:number|no\.?)?\s*[:#=]?\s*(\d{6,17})\b/gi,
|
|
60
|
+
valueGroup: 1,
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
function luhnValid(candidate) {
|
|
65
|
+
const digits = candidate.replace(/\D/g, "");
|
|
66
|
+
if (digits.length < 13 || digits.length > 19) return false;
|
|
67
|
+
let sum = 0;
|
|
68
|
+
let doubleDigit = false;
|
|
69
|
+
for (let i = digits.length - 1; i >= 0; i -= 1) {
|
|
70
|
+
let n = Number(digits[i]);
|
|
71
|
+
if (doubleDigit) {
|
|
72
|
+
n *= 2;
|
|
73
|
+
if (n > 9) n -= 9;
|
|
74
|
+
}
|
|
75
|
+
sum += n;
|
|
76
|
+
doubleDigit = !doubleDigit;
|
|
77
|
+
}
|
|
78
|
+
return sum % 10 === 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function applyFinding(text, match, pattern) {
|
|
82
|
+
const raw = match[0];
|
|
83
|
+
const value = pattern.valueGroup ? match[pattern.valueGroup] : raw;
|
|
84
|
+
const valueOffset = pattern.valueGroup ? raw.indexOf(value) : 0;
|
|
85
|
+
const start = match.index + valueOffset;
|
|
86
|
+
const end = start + value.length;
|
|
87
|
+
return {
|
|
88
|
+
sanitized:
|
|
89
|
+
text.slice(0, start) + PLACEHOLDERS[pattern.entityType] + text.slice(end),
|
|
90
|
+
finding: {
|
|
91
|
+
entityType: pattern.entityType,
|
|
92
|
+
confidence: pattern.confidence,
|
|
93
|
+
range: { start, end },
|
|
94
|
+
},
|
|
95
|
+
delta: PLACEHOLDERS[pattern.entityType].length - value.length,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function collectCreditCardFindings(text) {
|
|
100
|
+
const findings = [];
|
|
101
|
+
const re = /\b(?:\d[ -]?){13,19}\b/g;
|
|
102
|
+
let match;
|
|
103
|
+
while ((match = re.exec(text)) !== null) {
|
|
104
|
+
const value = match[0].trim();
|
|
105
|
+
if (!luhnValid(value)) continue;
|
|
106
|
+
findings.push({
|
|
107
|
+
entityType: "credit_card",
|
|
108
|
+
confidence: "high",
|
|
109
|
+
range: { start: match.index, end: match.index + match[0].length },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return findings;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function summarizeFindings(sourceMetadata, findings) {
|
|
116
|
+
const sourceId =
|
|
117
|
+
sourceMetadata.sourceId ??
|
|
118
|
+
sourceMetadata.id ??
|
|
119
|
+
sourceMetadata.path ??
|
|
120
|
+
sourceMetadata.url ??
|
|
121
|
+
"unknown";
|
|
122
|
+
const byType = new Map();
|
|
123
|
+
for (const finding of findings) {
|
|
124
|
+
const current = byType.get(finding.entityType) ?? {
|
|
125
|
+
sourceId,
|
|
126
|
+
entityType: finding.entityType,
|
|
127
|
+
confidence: finding.confidence,
|
|
128
|
+
count: 0,
|
|
129
|
+
ranges: [],
|
|
130
|
+
};
|
|
131
|
+
current.count += 1;
|
|
132
|
+
current.ranges.push(finding.range);
|
|
133
|
+
byType.set(finding.entityType, current);
|
|
134
|
+
}
|
|
135
|
+
return [...byType.values()].sort((a, b) =>
|
|
136
|
+
a.entityType.localeCompare(b.entityType)
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function collectFindings(rawText) {
|
|
141
|
+
const text = String(rawText ?? "");
|
|
142
|
+
const findings = [];
|
|
143
|
+
|
|
144
|
+
for (const pattern of PATTERNS) {
|
|
145
|
+
pattern.re.lastIndex = 0;
|
|
146
|
+
let match;
|
|
147
|
+
while ((match = pattern.re.exec(text)) !== null) {
|
|
148
|
+
findings.push(applyFinding(text, match, pattern).finding);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
findings.push(...collectCreditCardFindings(text));
|
|
152
|
+
findings.sort((a, b) => a.range.start - b.range.start);
|
|
153
|
+
return findings;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function scanWikiSourceText(rawText, sourceMetadata = {}) {
|
|
157
|
+
const findings = collectFindings(rawText);
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
sourceId:
|
|
161
|
+
sourceMetadata.sourceId ??
|
|
162
|
+
sourceMetadata.id ??
|
|
163
|
+
sourceMetadata.path ??
|
|
164
|
+
sourceMetadata.url ??
|
|
165
|
+
"unknown",
|
|
166
|
+
reviewRequired: findings.length > 0,
|
|
167
|
+
findings: summarizeFindings(sourceMetadata, findings),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function sanitizeWikiSourceText(rawText, sourceMetadata = {}) {
|
|
172
|
+
let sanitized = String(rawText ?? "");
|
|
173
|
+
const rawFindings = collectFindings(rawText);
|
|
174
|
+
for (const finding of [...rawFindings].sort(
|
|
175
|
+
(a, b) => b.range.start - a.range.start
|
|
176
|
+
)) {
|
|
177
|
+
sanitized =
|
|
178
|
+
sanitized.slice(0, finding.range.start) +
|
|
179
|
+
PLACEHOLDERS[finding.entityType] +
|
|
180
|
+
sanitized.slice(finding.range.end);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
text: sanitized,
|
|
185
|
+
reviewRequired: rawFindings.length > 0,
|
|
186
|
+
findings: summarizeFindings(sourceMetadata, rawFindings),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function serializeWikiSafetyFindings(result) {
|
|
191
|
+
return JSON.stringify(
|
|
192
|
+
{
|
|
193
|
+
reviewRequired: Boolean(result?.reviewRequired),
|
|
194
|
+
findings: Array.isArray(result?.findings) ? result.findings : [],
|
|
195
|
+
},
|
|
196
|
+
null,
|
|
197
|
+
2
|
|
198
|
+
);
|
|
199
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic wiki source safety helpers.
|
|
4
|
+
*
|
|
5
|
+
* This module intentionally uses only Node built-ins and pure string scanning so
|
|
6
|
+
* downstream wiki connectors can run the same redaction pass before persisting
|
|
7
|
+
* reader-safe source notes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const PLACEHOLDERS = {
|
|
11
|
+
ssn: "[REDACTED:SSN]",
|
|
12
|
+
credit_card: "[REDACTED:CREDIT_CARD]",
|
|
13
|
+
private_key: "[REDACTED:PRIVATE_KEY]",
|
|
14
|
+
password: "[REDACTED:PASSWORD]",
|
|
15
|
+
api_key: "[REDACTED:API_KEY]",
|
|
16
|
+
oauth_token: "[REDACTED:OAUTH_TOKEN]",
|
|
17
|
+
bank_account: "[REDACTED:BANK_ACCOUNT]",
|
|
18
|
+
routing_number: "[REDACTED:ROUTING_NUMBER]",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const PATTERNS = [
|
|
22
|
+
{
|
|
23
|
+
entityType: "private_key",
|
|
24
|
+
confidence: "high",
|
|
25
|
+
re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/g,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
entityType: "ssn",
|
|
29
|
+
confidence: "high",
|
|
30
|
+
re: /\b(?!000|666|9\d\d)\d{3}-(?!00)\d{2}-(?!0000)\d{4}\b/g,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
entityType: "password",
|
|
34
|
+
confidence: "high",
|
|
35
|
+
re: /\b(?:password|passwd|pwd)\s*[:=]\s*(['"]?)([^\s'",;]{8,})\1/gi,
|
|
36
|
+
valueGroup: 2,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
entityType: "api_key",
|
|
40
|
+
confidence: "medium",
|
|
41
|
+
re: /\b(?:api[_-]?key|access[_-]?key|secret[_-]?key|client[_-]?secret)\s*[:=]\s*(['"]?)([A-Za-z0-9._-]{20,})\1/gi,
|
|
42
|
+
valueGroup: 2,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
entityType: "oauth_token",
|
|
46
|
+
confidence: "high",
|
|
47
|
+
re: /\b(?:oauth[_-]?token|refresh[_-]?token|access[_-]?token|bearer)\s*[:= ]\s*(['"]?)([A-Za-z0-9._-]{24,})\1/gi,
|
|
48
|
+
valueGroup: 2,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
entityType: "routing_number",
|
|
52
|
+
confidence: "medium",
|
|
53
|
+
re: /\b(?:routing|routing_number|aba)\s*(?:number|no\.?)?\s*[:#=]?\s*(\d{9})\b/gi,
|
|
54
|
+
valueGroup: 1,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
entityType: "bank_account",
|
|
58
|
+
confidence: "medium",
|
|
59
|
+
re: /\b(?:bank\s+)?(?:account|acct)\s*(?:number|no\.?)?\s*[:#=]?\s*(\d{6,17})\b/gi,
|
|
60
|
+
valueGroup: 1,
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
function luhnValid(candidate) {
|
|
65
|
+
const digits = candidate.replace(/\D/g, "");
|
|
66
|
+
if (digits.length < 13 || digits.length > 19) return false;
|
|
67
|
+
let sum = 0;
|
|
68
|
+
let doubleDigit = false;
|
|
69
|
+
for (let i = digits.length - 1; i >= 0; i -= 1) {
|
|
70
|
+
let n = Number(digits[i]);
|
|
71
|
+
if (doubleDigit) {
|
|
72
|
+
n *= 2;
|
|
73
|
+
if (n > 9) n -= 9;
|
|
74
|
+
}
|
|
75
|
+
sum += n;
|
|
76
|
+
doubleDigit = !doubleDigit;
|
|
77
|
+
}
|
|
78
|
+
return sum % 10 === 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function applyFinding(text, match, pattern) {
|
|
82
|
+
const raw = match[0];
|
|
83
|
+
const value = pattern.valueGroup ? match[pattern.valueGroup] : raw;
|
|
84
|
+
const valueOffset = pattern.valueGroup ? raw.indexOf(value) : 0;
|
|
85
|
+
const start = match.index + valueOffset;
|
|
86
|
+
const end = start + value.length;
|
|
87
|
+
return {
|
|
88
|
+
sanitized:
|
|
89
|
+
text.slice(0, start) + PLACEHOLDERS[pattern.entityType] + text.slice(end),
|
|
90
|
+
finding: {
|
|
91
|
+
entityType: pattern.entityType,
|
|
92
|
+
confidence: pattern.confidence,
|
|
93
|
+
range: { start, end },
|
|
94
|
+
},
|
|
95
|
+
delta: PLACEHOLDERS[pattern.entityType].length - value.length,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function collectCreditCardFindings(text) {
|
|
100
|
+
const findings = [];
|
|
101
|
+
const re = /\b(?:\d[ -]?){13,19}\b/g;
|
|
102
|
+
let match;
|
|
103
|
+
while ((match = re.exec(text)) !== null) {
|
|
104
|
+
const value = match[0].trim();
|
|
105
|
+
if (!luhnValid(value)) continue;
|
|
106
|
+
findings.push({
|
|
107
|
+
entityType: "credit_card",
|
|
108
|
+
confidence: "high",
|
|
109
|
+
range: { start: match.index, end: match.index + match[0].length },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return findings;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function summarizeFindings(sourceMetadata, findings) {
|
|
116
|
+
const sourceId =
|
|
117
|
+
sourceMetadata.sourceId ??
|
|
118
|
+
sourceMetadata.id ??
|
|
119
|
+
sourceMetadata.path ??
|
|
120
|
+
sourceMetadata.url ??
|
|
121
|
+
"unknown";
|
|
122
|
+
const byType = new Map();
|
|
123
|
+
for (const finding of findings) {
|
|
124
|
+
const current = byType.get(finding.entityType) ?? {
|
|
125
|
+
sourceId,
|
|
126
|
+
entityType: finding.entityType,
|
|
127
|
+
confidence: finding.confidence,
|
|
128
|
+
count: 0,
|
|
129
|
+
ranges: [],
|
|
130
|
+
};
|
|
131
|
+
current.count += 1;
|
|
132
|
+
current.ranges.push(finding.range);
|
|
133
|
+
byType.set(finding.entityType, current);
|
|
134
|
+
}
|
|
135
|
+
return [...byType.values()].sort((a, b) =>
|
|
136
|
+
a.entityType.localeCompare(b.entityType)
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function collectFindings(rawText) {
|
|
141
|
+
const text = String(rawText ?? "");
|
|
142
|
+
const findings = [];
|
|
143
|
+
|
|
144
|
+
for (const pattern of PATTERNS) {
|
|
145
|
+
pattern.re.lastIndex = 0;
|
|
146
|
+
let match;
|
|
147
|
+
while ((match = pattern.re.exec(text)) !== null) {
|
|
148
|
+
findings.push(applyFinding(text, match, pattern).finding);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
findings.push(...collectCreditCardFindings(text));
|
|
152
|
+
findings.sort((a, b) => a.range.start - b.range.start);
|
|
153
|
+
return findings;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function scanWikiSourceText(rawText, sourceMetadata = {}) {
|
|
157
|
+
const findings = collectFindings(rawText);
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
sourceId:
|
|
161
|
+
sourceMetadata.sourceId ??
|
|
162
|
+
sourceMetadata.id ??
|
|
163
|
+
sourceMetadata.path ??
|
|
164
|
+
sourceMetadata.url ??
|
|
165
|
+
"unknown",
|
|
166
|
+
reviewRequired: findings.length > 0,
|
|
167
|
+
findings: summarizeFindings(sourceMetadata, findings),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function sanitizeWikiSourceText(rawText, sourceMetadata = {}) {
|
|
172
|
+
let sanitized = String(rawText ?? "");
|
|
173
|
+
const rawFindings = collectFindings(rawText);
|
|
174
|
+
for (const finding of [...rawFindings].sort(
|
|
175
|
+
(a, b) => b.range.start - a.range.start
|
|
176
|
+
)) {
|
|
177
|
+
sanitized =
|
|
178
|
+
sanitized.slice(0, finding.range.start) +
|
|
179
|
+
PLACEHOLDERS[finding.entityType] +
|
|
180
|
+
sanitized.slice(finding.range.end);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
text: sanitized,
|
|
185
|
+
reviewRequired: rawFindings.length > 0,
|
|
186
|
+
findings: summarizeFindings(sourceMetadata, rawFindings),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function serializeWikiSafetyFindings(result) {
|
|
191
|
+
return JSON.stringify(
|
|
192
|
+
{
|
|
193
|
+
reviewRequired: Boolean(result?.reviewRequired),
|
|
194
|
+
findings: Array.isArray(result?.findings) ? result.findings : [],
|
|
195
|
+
},
|
|
196
|
+
null,
|
|
197
|
+
2
|
|
198
|
+
);
|
|
199
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic wiki source safety helpers.
|
|
4
|
+
*
|
|
5
|
+
* This module intentionally uses only Node built-ins and pure string scanning so
|
|
6
|
+
* downstream wiki connectors can run the same redaction pass before persisting
|
|
7
|
+
* reader-safe source notes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const PLACEHOLDERS = {
|
|
11
|
+
ssn: "[REDACTED:SSN]",
|
|
12
|
+
credit_card: "[REDACTED:CREDIT_CARD]",
|
|
13
|
+
private_key: "[REDACTED:PRIVATE_KEY]",
|
|
14
|
+
password: "[REDACTED:PASSWORD]",
|
|
15
|
+
api_key: "[REDACTED:API_KEY]",
|
|
16
|
+
oauth_token: "[REDACTED:OAUTH_TOKEN]",
|
|
17
|
+
bank_account: "[REDACTED:BANK_ACCOUNT]",
|
|
18
|
+
routing_number: "[REDACTED:ROUTING_NUMBER]",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const PATTERNS = [
|
|
22
|
+
{
|
|
23
|
+
entityType: "private_key",
|
|
24
|
+
confidence: "high",
|
|
25
|
+
re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/g,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
entityType: "ssn",
|
|
29
|
+
confidence: "high",
|
|
30
|
+
re: /\b(?!000|666|9\d\d)\d{3}-(?!00)\d{2}-(?!0000)\d{4}\b/g,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
entityType: "password",
|
|
34
|
+
confidence: "high",
|
|
35
|
+
re: /\b(?:password|passwd|pwd)\s*[:=]\s*(['"]?)([^\s'",;]{8,})\1/gi,
|
|
36
|
+
valueGroup: 2,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
entityType: "api_key",
|
|
40
|
+
confidence: "medium",
|
|
41
|
+
re: /\b(?:api[_-]?key|access[_-]?key|secret[_-]?key|client[_-]?secret)\s*[:=]\s*(['"]?)([A-Za-z0-9._-]{20,})\1/gi,
|
|
42
|
+
valueGroup: 2,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
entityType: "oauth_token",
|
|
46
|
+
confidence: "high",
|
|
47
|
+
re: /\b(?:oauth[_-]?token|refresh[_-]?token|access[_-]?token|bearer)\s*[:= ]\s*(['"]?)([A-Za-z0-9._-]{24,})\1/gi,
|
|
48
|
+
valueGroup: 2,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
entityType: "routing_number",
|
|
52
|
+
confidence: "medium",
|
|
53
|
+
re: /\b(?:routing|routing_number|aba)\s*(?:number|no\.?)?\s*[:#=]?\s*(\d{9})\b/gi,
|
|
54
|
+
valueGroup: 1,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
entityType: "bank_account",
|
|
58
|
+
confidence: "medium",
|
|
59
|
+
re: /\b(?:bank\s+)?(?:account|acct)\s*(?:number|no\.?)?\s*[:#=]?\s*(\d{6,17})\b/gi,
|
|
60
|
+
valueGroup: 1,
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
function luhnValid(candidate) {
|
|
65
|
+
const digits = candidate.replace(/\D/g, "");
|
|
66
|
+
if (digits.length < 13 || digits.length > 19) return false;
|
|
67
|
+
let sum = 0;
|
|
68
|
+
let doubleDigit = false;
|
|
69
|
+
for (let i = digits.length - 1; i >= 0; i -= 1) {
|
|
70
|
+
let n = Number(digits[i]);
|
|
71
|
+
if (doubleDigit) {
|
|
72
|
+
n *= 2;
|
|
73
|
+
if (n > 9) n -= 9;
|
|
74
|
+
}
|
|
75
|
+
sum += n;
|
|
76
|
+
doubleDigit = !doubleDigit;
|
|
77
|
+
}
|
|
78
|
+
return sum % 10 === 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function applyFinding(text, match, pattern) {
|
|
82
|
+
const raw = match[0];
|
|
83
|
+
const value = pattern.valueGroup ? match[pattern.valueGroup] : raw;
|
|
84
|
+
const valueOffset = pattern.valueGroup ? raw.indexOf(value) : 0;
|
|
85
|
+
const start = match.index + valueOffset;
|
|
86
|
+
const end = start + value.length;
|
|
87
|
+
return {
|
|
88
|
+
sanitized:
|
|
89
|
+
text.slice(0, start) + PLACEHOLDERS[pattern.entityType] + text.slice(end),
|
|
90
|
+
finding: {
|
|
91
|
+
entityType: pattern.entityType,
|
|
92
|
+
confidence: pattern.confidence,
|
|
93
|
+
range: { start, end },
|
|
94
|
+
},
|
|
95
|
+
delta: PLACEHOLDERS[pattern.entityType].length - value.length,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function collectCreditCardFindings(text) {
|
|
100
|
+
const findings = [];
|
|
101
|
+
const re = /\b(?:\d[ -]?){13,19}\b/g;
|
|
102
|
+
let match;
|
|
103
|
+
while ((match = re.exec(text)) !== null) {
|
|
104
|
+
const value = match[0].trim();
|
|
105
|
+
if (!luhnValid(value)) continue;
|
|
106
|
+
findings.push({
|
|
107
|
+
entityType: "credit_card",
|
|
108
|
+
confidence: "high",
|
|
109
|
+
range: { start: match.index, end: match.index + match[0].length },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return findings;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function summarizeFindings(sourceMetadata, findings) {
|
|
116
|
+
const sourceId =
|
|
117
|
+
sourceMetadata.sourceId ??
|
|
118
|
+
sourceMetadata.id ??
|
|
119
|
+
sourceMetadata.path ??
|
|
120
|
+
sourceMetadata.url ??
|
|
121
|
+
"unknown";
|
|
122
|
+
const byType = new Map();
|
|
123
|
+
for (const finding of findings) {
|
|
124
|
+
const current = byType.get(finding.entityType) ?? {
|
|
125
|
+
sourceId,
|
|
126
|
+
entityType: finding.entityType,
|
|
127
|
+
confidence: finding.confidence,
|
|
128
|
+
count: 0,
|
|
129
|
+
ranges: [],
|
|
130
|
+
};
|
|
131
|
+
current.count += 1;
|
|
132
|
+
current.ranges.push(finding.range);
|
|
133
|
+
byType.set(finding.entityType, current);
|
|
134
|
+
}
|
|
135
|
+
return [...byType.values()].sort((a, b) =>
|
|
136
|
+
a.entityType.localeCompare(b.entityType)
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function collectFindings(rawText) {
|
|
141
|
+
const text = String(rawText ?? "");
|
|
142
|
+
const findings = [];
|
|
143
|
+
|
|
144
|
+
for (const pattern of PATTERNS) {
|
|
145
|
+
pattern.re.lastIndex = 0;
|
|
146
|
+
let match;
|
|
147
|
+
while ((match = pattern.re.exec(text)) !== null) {
|
|
148
|
+
findings.push(applyFinding(text, match, pattern).finding);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
findings.push(...collectCreditCardFindings(text));
|
|
152
|
+
findings.sort((a, b) => a.range.start - b.range.start);
|
|
153
|
+
return findings;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function scanWikiSourceText(rawText, sourceMetadata = {}) {
|
|
157
|
+
const findings = collectFindings(rawText);
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
sourceId:
|
|
161
|
+
sourceMetadata.sourceId ??
|
|
162
|
+
sourceMetadata.id ??
|
|
163
|
+
sourceMetadata.path ??
|
|
164
|
+
sourceMetadata.url ??
|
|
165
|
+
"unknown",
|
|
166
|
+
reviewRequired: findings.length > 0,
|
|
167
|
+
findings: summarizeFindings(sourceMetadata, findings),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function sanitizeWikiSourceText(rawText, sourceMetadata = {}) {
|
|
172
|
+
let sanitized = String(rawText ?? "");
|
|
173
|
+
const rawFindings = collectFindings(rawText);
|
|
174
|
+
for (const finding of [...rawFindings].sort(
|
|
175
|
+
(a, b) => b.range.start - a.range.start
|
|
176
|
+
)) {
|
|
177
|
+
sanitized =
|
|
178
|
+
sanitized.slice(0, finding.range.start) +
|
|
179
|
+
PLACEHOLDERS[finding.entityType] +
|
|
180
|
+
sanitized.slice(finding.range.end);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
text: sanitized,
|
|
185
|
+
reviewRequired: rawFindings.length > 0,
|
|
186
|
+
findings: summarizeFindings(sourceMetadata, rawFindings),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function serializeWikiSafetyFindings(result) {
|
|
191
|
+
return JSON.stringify(
|
|
192
|
+
{
|
|
193
|
+
reviewRequired: Boolean(result?.reviewRequired),
|
|
194
|
+
findings: Array.isArray(result?.findings) ? result.findings : [],
|
|
195
|
+
},
|
|
196
|
+
null,
|
|
197
|
+
2
|
|
198
|
+
);
|
|
199
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Repair counterpart to /lisa:intake. Vendor-agnostic batch scanner that finds stuck or half-closed work — items left in `blocked`, stalled in an in-progress role (build `claimed`, PRD `in_review`), terminal-labeled items still natively open,
|
|
2
|
+
description: "Repair counterpart to /lisa:intake. Vendor-agnostic batch scanner that finds stuck or half-closed work — items left in `blocked`, stalled in an in-progress role (build `claimed`, PRD `in_review`), terminal-labeled items still natively open, rollups whose children are all terminal, and GitHub issues missing official Lisa lifecycle labels — across the same queues /lisa:intake serves (Notion / Confluence / Linear / GitHub PRDs; JIRA / GitHub / Linear build issues). Repairs every materially actionable candidate inside the `max_candidates` cap: resumes stalled in-progress work in place — but for a stalled build it first diagnoses the PR/deploy state and, if the PR cannot merge (conflict, rebase, failing checks, unaddressed CodeRabbit/changes-requested) or a deploy failed, files a build-ready fix ticket and moves the item to `blocked` (blocked by it) instead of re-dispatching — re-validates blocked PRDs, re-dispatches blocked build items whose blockers have cleared, performs terminal native closure, reconciles parent rollups to their derived state (including the intermediate-env case — e.g. all children at `On Stg` → parent `On Stg` — and a container wrongly stuck in `ready`), normalizes missing GitHub lifecycle labels to the configured PRD/build `ready` label, and closes out completed rollups. Cron-safe and bounded; default GitHub intake_mode is both and default max_candidates is 100."
|
|
3
3
|
argument-hint: "<Notion-PRD-database-URL | Confluence-space-URL | Confluence-parent-page-URL | Linear-workspace-URL | Linear-team-URL | GitHub-repo-URL | org/repo | JIRA-project-key | JQL-filter> [intake_mode=prd|build|both] [stale_after=2h] [max_candidates=100] [force=true]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Use the /lisa:repair-intake skill to scan the queue for stuck or half-closed items and repair every materially actionable candidate inside `max_candidates` (default 100). For GitHub queues, default `intake_mode` to `both` when the caller omits it. $ARGUMENTS
|
|
6
|
+
Use the /lisa:repair-intake skill to scan the queue for stuck or half-closed items, normalize GitHub issues missing official Lisa lifecycle labels into the configured PRD/build `ready` lane, and repair every materially actionable candidate inside `max_candidates` (default 100). For GitHub queues, default `intake_mode` to `both` when the caller omits it. $ARGUMENTS
|