@mmerterden/multi-agent-pipeline 14.2.2 → 15.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +186 -6
- package/README.md +19 -12
- package/README.tr.md +19 -12
- package/SECURITY.md +43 -0
- package/docs/FIGMA_PIPELINE.md +3 -3
- package/docs/adr/0006-skills-core-external-split.md +1 -1
- package/docs/adr/0007-multi-tool-adapter-framework.md +1 -1
- package/docs/adr/0009-claude-stack-skills-plugin-only.md +31 -0
- package/docs/adr/README.md +1 -0
- package/docs/architecture.md +13 -13
- package/docs/ecosystem.md +31 -31
- package/docs/features.md +5 -5
- package/index.js +6 -1
- package/install/_codex-agents.mjs +11 -2
- package/install/_common.mjs +109 -3
- package/install/_dev-only-files.mjs +0 -1
- package/install/_platform-filter.mjs +54 -113
- package/install/_plugin-skills.mjs +36 -36
- package/install/claude.mjs +251 -61
- package/install/codex.mjs +28 -6
- package/install/copilot.mjs +69 -9
- package/install/index.mjs +9 -3
- package/install/templates/codex-instructions.md +1 -1
- package/install/templates/copilot-instructions.md +3 -3
- package/package.json +2 -3
- package/pipeline/commands/multi-agent/SKILL.md +2 -0
- package/pipeline/commands/multi-agent/analysis/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/build-optimize/SKILL.md +9 -9
- package/pipeline/commands/multi-agent/channels/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +186 -0
- package/pipeline/commands/multi-agent/dev/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/dev-local/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/help/SKILL.md +19 -4
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/jira/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/prune-prompts/SKILL.md +81 -0
- package/pipeline/commands/multi-agent/refactor/SKILL.md +36 -1
- package/pipeline/commands/multi-agent/resume/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/{ship → resume-local}/SKILL.md +8 -8
- package/pipeline/commands/multi-agent/scan/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +5 -5
- package/pipeline/commands/multi-agent/stack/SKILL.md +62 -40
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/sync/SKILL.md +18 -11
- package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/uninstall/SKILL.md +2 -0
- package/pipeline/commands/multi-agent/update/SKILL.md +4 -4
- package/pipeline/lib/issue-fetcher.sh +1 -1
- package/pipeline/lib/parse-complaints.sh +316 -0
- package/pipeline/multi-agent-refs/channels/wiki.md +3 -3
- package/pipeline/multi-agent-refs/complaint-analysis-template.md +99 -0
- package/pipeline/multi-agent-refs/component-dispatch.md +6 -6
- package/pipeline/multi-agent-refs/cross-cli-contract.md +16 -16
- package/pipeline/multi-agent-refs/features/external-context-injection.md +1 -1
- package/pipeline/multi-agent-refs/features/stack-skill-routing.md +5 -5
- package/pipeline/multi-agent-refs/generate-issue.md +1 -1
- package/pipeline/multi-agent-refs/phases/modes.md +1 -1
- package/pipeline/multi-agent-refs/phases/operations.md +7 -1
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-2-planning.md +5 -5
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +3 -3
- package/pipeline/multi-agent-refs/phases/phase-4-review.md +12 -12
- package/pipeline/multi-agent-refs/phases/phase-5-test.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-7-report.md +6 -0
- package/pipeline/multi-agent-refs/tracker-contract.md +3 -2
- package/pipeline/multi-agent-refs/wiki-capture.md +2 -2
- package/pipeline/preferences-template.json +18 -5
- package/pipeline/rules/figma-pipeline.md +2 -2
- package/pipeline/schemas/agent-state.schema.json +1 -1
- package/pipeline/schemas/complaint-analysis-spec.schema.json +216 -0
- package/pipeline/schemas/migrations/prefs-2.5.0-to-2.6.0.mjs +46 -0
- package/pipeline/schemas/prefs.schema.json +296 -66
- package/pipeline/schemas/token-budget.json +2 -2
- package/pipeline/scripts/README.md +4 -3
- package/pipeline/scripts/_stack-routing.mjs +79 -0
- package/pipeline/scripts/audit-log-rotate.sh +4 -1
- package/pipeline/scripts/build-skills-index.mjs +11 -0
- package/pipeline/scripts/build-stack-plugins.mjs +28 -60
- package/pipeline/scripts/check-derived-drift.mjs +55 -28
- package/pipeline/scripts/gc-worktrees.sh +4 -1
- package/pipeline/scripts/gen-skills-index.mjs +1 -1
- package/pipeline/scripts/match-skills.mjs +12 -2
- package/pipeline/scripts/migrate-prefs.mjs +33 -21
- package/pipeline/scripts/phase-tracker.sh +32 -5
- package/pipeline/scripts/phase0-exit-gate.mjs +3 -2
- package/pipeline/scripts/run-aggregator.mjs +7 -2
- package/pipeline/scripts/scan-agent-config.sh +1 -1
- package/pipeline/scripts/skill-conformance.mjs +165 -30
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +1 -1
- package/pipeline/scripts/test-gap-rules/android.json +25 -0
- package/pipeline/scripts/test-gap-rules/ios.json +34 -0
- package/pipeline/scripts/test-gap-rules/node.json +29 -0
- package/pipeline/scripts/test-gap-rules/python.json +25 -0
- package/pipeline/scripts/uninstall.mjs +160 -11
- package/pipeline/scripts/usage-report.mjs +426 -0
- package/pipeline/scripts/validate-complaint-doc.mjs +250 -0
- package/pipeline/scripts/validate-reviewer.mjs +9 -3
- package/pipeline/skills/.skill-manifest.json +156 -108
- package/pipeline/skills/.skills-index.json +449 -12
- package/pipeline/skills/shared/README.md +14 -10
- package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-complaint-analysis/SKILL.md +49 -0
- package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-prune-prompts/SKILL.md +83 -0
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +153 -90
- package/pipeline/skills/shared/core/{multi-agent-ship → multi-agent-resume-local}/SKILL.md +6 -6
- package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +89 -22
- package/pipeline/skills/shared/core/multi-agent-store-ready/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +8 -8
- package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +1 -1
- package/pipeline/skills/shared/external/ios-coding-standard/modules/_TEMPLATE.yml +2 -2
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +368 -33
- package/pipeline/skills/shared/external/ios-coding-standard/references/swiftlint.draft.yml +1 -2
- package/pipeline/skills/shared/external/ios-coding-standard/scripts/check_structure.py +765 -0
- package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +75 -0
- package/pipeline/skills/shared/external/ios-module-structure/modules/_TEMPLATE.yml +131 -0
- package/pipeline/skills/shared/external/ios-module-structure/references/rules.yml +559 -0
- package/pipeline/skills/shared/external/ios-module-structure/scripts/check_structure.py +765 -0
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +53 -10
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +4 -3
- package/pipeline/skills/skills-index.md +7 -4
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// validate-complaint-doc.mjs - deterministic validator for an EMITTED
|
|
3
|
+
// /multi-agent:complaint-analysis report (complaints/<run-name>.md).
|
|
4
|
+
//
|
|
5
|
+
// The complaint-analysis SKILL states its "fails the dispatch gate" invariants
|
|
6
|
+
// as prose; this turns the mechanically checkable ones into a real gate so a
|
|
7
|
+
// malformed or PII-leaking report is caught before it reaches Confluence,
|
|
8
|
+
// Jira, or a repo working tree.
|
|
9
|
+
//
|
|
10
|
+
// Zero deps. Reads one markdown file (path arg or STDIN).
|
|
11
|
+
//
|
|
12
|
+
// Checks (ERROR = blocking exit 1; WARN = advisory, still exit 0 unless --strict):
|
|
13
|
+
// - Front-matter block with required keys (run_name, generated_at, language,
|
|
14
|
+
// complaint_count, graylog_degraded); language in {tr, en}.
|
|
15
|
+
// - Required sections present by bilingual title keyword: Summary, Triage,
|
|
16
|
+
// Complaint Details, Open Questions, Methodology, References.
|
|
17
|
+
// - Every triage-table row (a row in the Triage section whose FIRST cell is
|
|
18
|
+
// a C-NN id) has a valid verdict token in a cell of its own:
|
|
19
|
+
// client:<layer> | bff:<layer> | core | insufficient-evidence.
|
|
20
|
+
// Verdict tokens stay English in both languages (payload vocabulary).
|
|
21
|
+
// Scoping matters: the Section 1 coverage table cites C-NN ids in its
|
|
22
|
+
// "Verdicts attributed" column and must not be read as triage rows, and a
|
|
23
|
+
// summary cell mentioning "core" must not classify the row as core.
|
|
24
|
+
// - Every core verdict has a matching entry in the routing section
|
|
25
|
+
// (Yönlendirme / Routing), and that section exists when any core row does.
|
|
26
|
+
// - Every client/bff verdict has a fix-plan marker (Fix plan / Geliştirme
|
|
27
|
+
// planı) in the complaint-details section (development handoff, Locked 13).
|
|
28
|
+
// - Humanizer punctuation policy: no em-dash / en-dash / ellipsis /
|
|
29
|
+
// section-sign / curly quotes anywhere.
|
|
30
|
+
// - Redaction leak scan: an email address or a 13-19 digit card-like run is
|
|
31
|
+
// an ERROR; a bare 11-digit run is a WARN (could be a numeric trx id); a
|
|
32
|
+
// PNR-shaped token (6-char uppercase letter+digit mix, or 6 uppercase
|
|
33
|
+
// letters right after a "PNR" keyword) is a WARN.
|
|
34
|
+
//
|
|
35
|
+
// Usage:
|
|
36
|
+
// node validate-complaint-doc.mjs complaints/complaints-20260810.md
|
|
37
|
+
// cat report.md | node validate-complaint-doc.mjs -
|
|
38
|
+
// node validate-complaint-doc.mjs report.md --strict # WARN also fails
|
|
39
|
+
//
|
|
40
|
+
// Exit: 0 valid, 1 invalid (or WARN under --strict), 64 usage error.
|
|
41
|
+
|
|
42
|
+
import { readFileSync } from "node:fs";
|
|
43
|
+
|
|
44
|
+
const REQUIRED_FM = ["run_name", "generated_at", "language", "complaint_count", "graylog_degraded"];
|
|
45
|
+
|
|
46
|
+
const REQUIRED_SECTIONS = [
|
|
47
|
+
{ key: "summary", any: ["Summary", "Özet", "Ozet"] },
|
|
48
|
+
{ key: "triage table", any: ["Triage"] },
|
|
49
|
+
{ key: "complaint details", any: ["Complaint Details", "Şikayet Detayları", "Sikayet Detaylari", "Detay"] },
|
|
50
|
+
{ key: "open questions", any: ["Open Questions", "Açık Sorular", "Acik Sorular"] },
|
|
51
|
+
{ key: "methodology", any: ["Methodology", "Metodoloji"] },
|
|
52
|
+
{ key: "references", any: ["References", "Referanslar"] },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const ROUTING_KEYWORDS = ["Routing", "Yönlendirme", "Yonlendirme"];
|
|
56
|
+
const FIX_PLAN_RE = /(Fix plan|Fix Plan|Geliştirme planı|Geliştirme Planı|Gelistirme plani|Gelistirme Plani)/;
|
|
57
|
+
|
|
58
|
+
// Cell-exact on purpose: a substring match would classify a row as `core`
|
|
59
|
+
// because its summary says "core-data" or "core team" before the verdict column.
|
|
60
|
+
const VERDICT_CELL_RE =
|
|
61
|
+
/^(client:(ios|android|web)|bff:(mobile-bff|web-bff)|core|insufficient-evidence)$/;
|
|
62
|
+
|
|
63
|
+
const BANNED_PUNCT = [
|
|
64
|
+
{ ch: "—", name: "em-dash" },
|
|
65
|
+
{ ch: "–", name: "en-dash" },
|
|
66
|
+
{ ch: "…", name: "ellipsis" },
|
|
67
|
+
{ ch: "§", name: "section-sign" },
|
|
68
|
+
{ ch: "“", name: "curly-double-open" },
|
|
69
|
+
{ ch: "”", name: "curly-double-close" },
|
|
70
|
+
{ ch: "‘", name: "curly-single-open" },
|
|
71
|
+
{ ch: "’", name: "curly-single-close" },
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/;
|
|
75
|
+
const CARD_RE = /\b\d(?:[ -]?\d){12,18}\b/;
|
|
76
|
+
const NATIONAL_ID_RE = /\b\d{11}\b/;
|
|
77
|
+
// Same shape parse-complaints.sh redacts (6-char uppercase alnum with at least
|
|
78
|
+
// one letter AND one digit), so a leak the parser should have caught fails
|
|
79
|
+
// loudly here too. All-letter PNRs are only flaggable with context: a bare
|
|
80
|
+
// 6-letter word is far too common, so those warn only right after "PNR".
|
|
81
|
+
const PNR_MIXED_RE = /\b(?=[A-Z0-9]{6}\b)(?=[A-Z0-9]*\d)(?=\d*[A-Z])[A-Z0-9]{6}\b/;
|
|
82
|
+
const PNR_CONTEXT_RE = /\bPNR\b[^A-Za-z0-9\n]{0,3}[A-Z]{6}\b/;
|
|
83
|
+
|
|
84
|
+
function readInput() {
|
|
85
|
+
const args = process.argv.slice(2).filter((a) => a !== "--strict");
|
|
86
|
+
const arg = args[0];
|
|
87
|
+
if (!arg) {
|
|
88
|
+
console.error("usage: validate-complaint-doc.mjs <path|-> [--strict]");
|
|
89
|
+
process.exit(64);
|
|
90
|
+
}
|
|
91
|
+
if (arg === "-") return readFileSync(0, "utf-8");
|
|
92
|
+
return readFileSync(arg, "utf-8");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function parseFrontMatter(text) {
|
|
96
|
+
const lines = text.split("\n");
|
|
97
|
+
if (lines[0].trim() !== "---") return null;
|
|
98
|
+
const end = lines.indexOf("---", 1);
|
|
99
|
+
if (end < 0) return null;
|
|
100
|
+
const fm = {};
|
|
101
|
+
for (let i = 1; i < end; i++) {
|
|
102
|
+
const m = lines[i].match(/^([A-Za-z_][A-Za-z0-9_]*):\s*(.*)$/);
|
|
103
|
+
if (m) fm[m[1]] = m[2].trim();
|
|
104
|
+
}
|
|
105
|
+
return { fm, bodyStart: end + 1 };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// The routing section body: from the heading whose title contains a routing
|
|
109
|
+
// keyword to the next heading of the same or shallower depth.
|
|
110
|
+
function sectionBody(text, keywords) {
|
|
111
|
+
const lines = text.split("\n");
|
|
112
|
+
let start = -1;
|
|
113
|
+
let depth = 0;
|
|
114
|
+
for (let i = 0; i < lines.length; i++) {
|
|
115
|
+
const m = lines[i].match(/^(#{1,3})\s+(.*)$/);
|
|
116
|
+
if (!m) continue;
|
|
117
|
+
if (start < 0 && keywords.some((kw) => m[2].includes(kw))) {
|
|
118
|
+
start = i;
|
|
119
|
+
depth = m[1].length;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (start >= 0 && m[1].length <= depth) {
|
|
123
|
+
return lines.slice(start, i).join("\n");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return start >= 0 ? lines.slice(start).join("\n") : null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function main() {
|
|
130
|
+
const text = readInput();
|
|
131
|
+
const strict = process.argv.includes("--strict");
|
|
132
|
+
const errors = [];
|
|
133
|
+
const warns = [];
|
|
134
|
+
|
|
135
|
+
// 1. Front-matter
|
|
136
|
+
const parsed = parseFrontMatter(text);
|
|
137
|
+
if (!parsed) {
|
|
138
|
+
errors.push("missing YAML front-matter block (--- ... ---) at the top");
|
|
139
|
+
} else {
|
|
140
|
+
for (const k of REQUIRED_FM) {
|
|
141
|
+
if (!parsed.fm[k]) errors.push(`front-matter missing required key: ${k}`);
|
|
142
|
+
}
|
|
143
|
+
const lang = parsed.fm.language;
|
|
144
|
+
if (lang && lang !== "tr" && lang !== "en") {
|
|
145
|
+
errors.push(`front-matter language must be tr|en, got: ${lang}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 2. Required sections (by heading keyword, bilingual)
|
|
150
|
+
const headings = text
|
|
151
|
+
.split("\n")
|
|
152
|
+
.filter((l) => /^#{1,3}\s/.test(l))
|
|
153
|
+
.map((l) => l.replace(/^#{1,3}\s/, "").trim());
|
|
154
|
+
const headingBlob = headings.join("\n");
|
|
155
|
+
for (const sec of REQUIRED_SECTIONS) {
|
|
156
|
+
if (!sec.any.some((kw) => headingBlob.includes(kw))) {
|
|
157
|
+
errors.push(`missing required section: ${sec.key}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 3. Triage rows: scoped to the Triage section (the Section 1 coverage table
|
|
162
|
+
// also cites C-NN ids, in its "Verdicts attributed" column), and a triage row
|
|
163
|
+
// carries the id in its FIRST cell. Every such row needs a verdict cell.
|
|
164
|
+
const triageScope = sectionBody(text, ["Triage"]) ?? text;
|
|
165
|
+
const rows = triageScope.split("\n").filter((l) => /^\s*\|\s*C-\d{2,}\s*\|/.test(l));
|
|
166
|
+
if (rows.length === 0) {
|
|
167
|
+
errors.push("no triage rows found (expected Triage-section rows with a C-NN id in the first cell)");
|
|
168
|
+
}
|
|
169
|
+
const coreIds = [];
|
|
170
|
+
const handoffIds = [];
|
|
171
|
+
for (const row of rows) {
|
|
172
|
+
const id = (row.match(/\bC-\d{2,}\b/) || [])[0];
|
|
173
|
+
const cells = row.split("|").map((c) => c.trim());
|
|
174
|
+
const verdict = cells.find((c) => VERDICT_CELL_RE.test(c));
|
|
175
|
+
if (!verdict) {
|
|
176
|
+
errors.push(`triage row ${id} has no valid verdict token (client:<layer> | bff:<layer> | core | insufficient-evidence)`);
|
|
177
|
+
} else if (verdict === "core" && !coreIds.includes(id)) {
|
|
178
|
+
coreIds.push(id);
|
|
179
|
+
} else if (/^(client|bff):/.test(verdict) && !handoffIds.includes(id)) {
|
|
180
|
+
handoffIds.push(id);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 4. Core verdicts require the routing section, one entry per complaint.
|
|
185
|
+
if (coreIds.length > 0) {
|
|
186
|
+
const routing = sectionBody(text, ROUTING_KEYWORDS);
|
|
187
|
+
if (!routing) {
|
|
188
|
+
errors.push(`core verdict(s) ${coreIds.join(", ")} but no routing section (Yönlendirme / Routing)`);
|
|
189
|
+
} else {
|
|
190
|
+
for (const id of coreIds) {
|
|
191
|
+
if (!routing.includes(id)) {
|
|
192
|
+
errors.push(`core verdict ${id} has no entry in the routing section`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 4b. Client/bff verdicts require a fix-plan block under their C-NN detail
|
|
199
|
+
// subsection (development handoff, Locked 13).
|
|
200
|
+
for (const id of handoffIds) {
|
|
201
|
+
const detail = sectionBody(text, [id]);
|
|
202
|
+
if (!detail || !FIX_PLAN_RE.test(detail)) {
|
|
203
|
+
errors.push(`client/bff verdict ${id} has no fix-plan block (Fix plan / Geliştirme planı) in its detail section`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 5. Humanizer punctuation
|
|
208
|
+
const bodyLines = text.split("\n");
|
|
209
|
+
for (let i = 0; i < bodyLines.length; i++) {
|
|
210
|
+
for (const b of BANNED_PUNCT) {
|
|
211
|
+
if (bodyLines[i].includes(b.ch)) {
|
|
212
|
+
errors.push(`banned punctuation ${b.name} at line ${i + 1} (humanizer policy)`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 6. Redaction leak scan
|
|
218
|
+
for (let i = 0; i < bodyLines.length; i++) {
|
|
219
|
+
if (EMAIL_RE.test(bodyLines[i])) {
|
|
220
|
+
errors.push(`redaction leak: email address at line ${i + 1}`);
|
|
221
|
+
}
|
|
222
|
+
if (CARD_RE.test(bodyLines[i])) {
|
|
223
|
+
errors.push(`redaction leak: card-like digit run at line ${i + 1}`);
|
|
224
|
+
}
|
|
225
|
+
if (NATIONAL_ID_RE.test(bodyLines[i]) && !CARD_RE.test(bodyLines[i])) {
|
|
226
|
+
warns.push(`possible redaction leak: bare 11-digit run at line ${i + 1} (national-id-like; ignore if it is a numeric trx id)`);
|
|
227
|
+
}
|
|
228
|
+
if (PNR_MIXED_RE.test(bodyLines[i]) || PNR_CONTEXT_RE.test(bodyLines[i])) {
|
|
229
|
+
warns.push(`possible redaction leak: PNR-shaped token at line ${i + 1} (parse-complaints.sh should have redacted it)`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Report
|
|
234
|
+
for (const w of warns) console.error(`WARN: ${w}`);
|
|
235
|
+
for (const e of errors) console.error(`ERROR: ${e}`);
|
|
236
|
+
if (errors.length > 0) {
|
|
237
|
+
console.error(
|
|
238
|
+
`validate-complaint-doc: FAIL (${errors.length} error(s), ${warns.length} warning(s))`,
|
|
239
|
+
);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
if (strict && warns.length > 0) {
|
|
243
|
+
console.error(`validate-complaint-doc: FAIL under --strict (${warns.length} warning(s))`);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
console.log(`validate-complaint-doc: OK (${warns.length} warning(s))`);
|
|
247
|
+
process.exit(0);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
main();
|
|
@@ -127,8 +127,13 @@ function validateConformance(parsed, selectedIds, errors) {
|
|
|
127
127
|
`${label} (${id}): verdict "conformant" needs the file it was checked in - a verdict with no evidence is an assertion`,
|
|
128
128
|
);
|
|
129
129
|
}
|
|
130
|
-
if (
|
|
131
|
-
|
|
130
|
+
if (
|
|
131
|
+
row.verdict === "not-applicable" &&
|
|
132
|
+
(typeof row.reason !== "string" || row.reason.length < 4)
|
|
133
|
+
) {
|
|
134
|
+
errors.push(
|
|
135
|
+
`${label} (${id}): verdict "not-applicable" needs a reason naming why the rule cannot bind`,
|
|
136
|
+
);
|
|
132
137
|
}
|
|
133
138
|
if (row.verdict === "violated") {
|
|
134
139
|
const matched = (parsed.findings ?? []).some((f) => f?.ruleId === id);
|
|
@@ -141,7 +146,8 @@ function validateConformance(parsed, selectedIds, errors) {
|
|
|
141
146
|
});
|
|
142
147
|
|
|
143
148
|
for (const [id, count] of seen) {
|
|
144
|
-
if (count > 1)
|
|
149
|
+
if (count > 1)
|
|
150
|
+
errors.push(`conformance: ruleId "${id}" appears ${count} times (expected exactly once)`);
|
|
145
151
|
}
|
|
146
152
|
const missing = [...selectedIds].filter((id) => !seen.has(id));
|
|
147
153
|
if (missing.length > 0) {
|