@nac3/forge-cli 1.21.3 → 1.23.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/chat/bridge.d.ts +43 -1
- package/dist/chat/bridge.d.ts.map +1 -1
- package/dist/chat/bridge.js +176 -31
- package/dist/chat/bridge.js.map +1 -1
- package/dist/chat/bridge_inbox.d.ts +143 -0
- package/dist/chat/bridge_inbox.d.ts.map +1 -0
- package/dist/chat/bridge_inbox.js +219 -0
- package/dist/chat/bridge_inbox.js.map +1 -0
- package/dist/chat/claude.d.ts +10 -0
- package/dist/chat/claude.d.ts.map +1 -1
- package/dist/chat/claude.js.map +1 -1
- package/dist/chat/panel.d.ts.map +1 -1
- package/dist/chat/panel.js +148 -16
- package/dist/chat/panel.js.map +1 -1
- package/dist/chat/recent_sessions_recap.d.ts.map +1 -1
- package/dist/chat/recent_sessions_recap.js +18 -6
- package/dist/chat/recent_sessions_recap.js.map +1 -1
- package/dist/chat/server.d.ts +1 -1
- package/dist/chat/server.d.ts.map +1 -1
- package/dist/chat/server.js +346 -53
- package/dist/chat/server.js.map +1 -1
- package/dist/chat/tools/bridge_verbs.d.ts +10 -0
- package/dist/chat/tools/bridge_verbs.d.ts.map +1 -1
- package/dist/chat/tools/bridge_verbs.js +66 -3
- package/dist/chat/tools/bridge_verbs.js.map +1 -1
- package/dist/chat/tools/nac3_invoke.d.ts.map +1 -1
- package/dist/chat/tools/nac3_invoke.js +20 -3
- package/dist/chat/tools/nac3_invoke.js.map +1 -1
- package/dist/chat/tools/pending.d.ts.map +1 -1
- package/dist/chat/tools/pending.js +27 -4
- package/dist/chat/tools/pending.js.map +1 -1
- package/dist/chat/tools/urazuke.d.ts.map +1 -1
- package/dist/chat/tools/urazuke.js +7 -3
- package/dist/chat/tools/urazuke.js.map +1 -1
- package/dist/chat/tools.d.ts +6 -0
- package/dist/chat/tools.d.ts.map +1 -1
- package/dist/chat/tools.js +4 -0
- package/dist/chat/tools.js.map +1 -1
- package/dist/chat/turn_control.d.ts +2 -0
- package/dist/chat/turn_control.d.ts.map +1 -1
- package/dist/chat/turn_control.js +30 -1
- package/dist/chat/turn_control.js.map +1 -1
- package/dist/commands/pending.d.ts.map +1 -1
- package/dist/commands/pending.js +4 -2
- package/dist/commands/pending.js.map +1 -1
- package/dist/commands/urazuke.d.ts.map +1 -1
- package/dist/commands/urazuke.js +206 -16
- package/dist/commands/urazuke.js.map +1 -1
- package/dist/core/cost_router.d.ts +7 -0
- package/dist/core/cost_router.d.ts.map +1 -1
- package/dist/core/cost_router.js +8 -5
- package/dist/core/cost_router.js.map +1 -1
- package/dist/core/limits_config.d.ts +37 -6
- package/dist/core/limits_config.d.ts.map +1 -1
- package/dist/core/limits_config.js +74 -8
- package/dist/core/limits_config.js.map +1 -1
- package/dist/core/whats_new.d.ts.map +1 -1
- package/dist/core/whats_new.js +12 -0
- package/dist/core/whats_new.js.map +1 -1
- package/dist/nac3/danger_zone.d.ts +39 -0
- package/dist/nac3/danger_zone.d.ts.map +1 -0
- package/dist/nac3/danger_zone.js +31 -0
- package/dist/nac3/danger_zone.js.map +1 -0
- package/dist/nac3/internal_manifest.d.ts.map +1 -1
- package/dist/nac3/internal_manifest.js +33 -0
- package/dist/nac3/internal_manifest.js.map +1 -1
- package/dist/pending/store.d.ts +7 -0
- package/dist/pending/store.d.ts.map +1 -1
- package/dist/pending/store.js +41 -0
- package/dist/pending/store.js.map +1 -1
- package/dist/urazuke/catalog.d.ts +1 -1
- package/dist/urazuke/catalog.d.ts.map +1 -1
- package/dist/urazuke/catalog.js.map +1 -1
- package/dist/urazuke/current.d.ts +2 -2
- package/dist/urazuke/current.d.ts.map +1 -1
- package/dist/urazuke/current.js +32 -12
- package/dist/urazuke/current.js.map +1 -1
- package/dist/urazuke/gate.d.ts +45 -7
- package/dist/urazuke/gate.d.ts.map +1 -1
- package/dist/urazuke/gate.js +190 -20
- package/dist/urazuke/gate.js.map +1 -1
- package/dist/urazuke/gates_data.d.ts +170 -0
- package/dist/urazuke/gates_data.d.ts.map +1 -1
- package/dist/urazuke/gates_data.js +98 -5
- package/dist/urazuke/gates_data.js.map +1 -1
- package/dist/urazuke/gates_ledger.d.ts +75 -0
- package/dist/urazuke/gates_ledger.d.ts.map +1 -0
- package/dist/urazuke/gates_ledger.js +183 -0
- package/dist/urazuke/gates_ledger.js.map +1 -0
- package/dist/urazuke/gates_pull.d.ts +97 -0
- package/dist/urazuke/gates_pull.d.ts.map +1 -0
- package/dist/urazuke/gates_pull.js +303 -0
- package/dist/urazuke/gates_pull.js.map +1 -0
- package/dist/urazuke/heuristics.d.ts +108 -0
- package/dist/urazuke/heuristics.d.ts.map +1 -0
- package/dist/urazuke/heuristics.js +248 -0
- package/dist/urazuke/heuristics.js.map +1 -0
- package/dist/urazuke/manual_cases.d.ts +31 -0
- package/dist/urazuke/manual_cases.d.ts.map +1 -0
- package/dist/urazuke/manual_cases.js +87 -0
- package/dist/urazuke/manual_cases.js.map +1 -0
- package/dist/urazuke/project_config.d.ts +25 -0
- package/dist/urazuke/project_config.d.ts.map +1 -0
- package/dist/urazuke/project_config.js +65 -0
- package/dist/urazuke/project_config.js.map +1 -0
- package/dist/urazuke/quality.d.ts +0 -8
- package/dist/urazuke/quality.d.ts.map +1 -1
- package/dist/urazuke/quality.js +4 -29
- package/dist/urazuke/quality.js.map +1 -1
- package/dist/urazuke/retro.d.ts.map +1 -1
- package/dist/urazuke/retro.js +4 -1
- package/dist/urazuke/retro.js.map +1 -1
- package/dist/urazuke/scoreboard.d.ts +85 -0
- package/dist/urazuke/scoreboard.d.ts.map +1 -0
- package/dist/urazuke/scoreboard.js +260 -0
- package/dist/urazuke/scoreboard.js.map +1 -0
- package/dist/urazuke/section.d.ts +2 -2
- package/dist/urazuke/section.d.ts.map +1 -1
- package/dist/urazuke/section.js +31 -6
- package/dist/urazuke/section.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workflow/orchestrator.d.ts +9 -0
- package/dist/workflow/orchestrator.d.ts.map +1 -1
- package/dist/workflow/orchestrator.js +17 -16
- package/dist/workflow/orchestrator.js.map +1 -1
- package/package.json +1 -1
- package/src/i18n/catalogs/ar.json +558 -550
- package/src/i18n/catalogs/de.json +558 -550
- package/src/i18n/catalogs/en.json +558 -550
- package/src/i18n/catalogs/es.json +558 -550
- package/src/i18n/catalogs/fr.json +558 -550
- package/src/i18n/catalogs/hi.json +558 -550
- package/src/i18n/catalogs/it.json +558 -550
- package/src/i18n/catalogs/ja.json +558 -550
- package/src/i18n/catalogs/pt.json +558 -550
- package/src/i18n/catalogs/zh.json +558 -550
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE FORTY-FIVE RULES, RESOLVED AGAINST THE DEFINITION IN FORCE
|
|
3
|
+
* (Annex H, layers 1 to 3 -- pieces 1, 2, 3 reaching the evaluator).
|
|
4
|
+
*
|
|
5
|
+
* Everything in here is READ from the gate definition the project is being
|
|
6
|
+
* measured by: the rule ids, the group they belong to, the instrument, the
|
|
7
|
+
* severity, which rules a machine may not judge (`human_red`), the
|
|
8
|
+
* thresholds and which of them the method declares. Nothing is a constant
|
|
9
|
+
* of Forge. That is the whole point: on 2026-10-27 this tool was measuring
|
|
10
|
+
* two thresholds it had invented (`max_complexity=10` standing in for
|
|
11
|
+
* CQ-04's undeclared `paths_per_function`, and a `dup_block_lines=8` with
|
|
12
|
+
* no rule id at all), which is the one thing the annex forbids outright.
|
|
13
|
+
*
|
|
14
|
+
* THE SHAPE OF THE PROJECT'S OWN DECLARATIONS is published too, and the
|
|
15
|
+
* field names are the method's, not ours: frontend, real_users, switches,
|
|
16
|
+
* switched_off, thresholds, tools (`project_config.fields`). They live in
|
|
17
|
+
* <root>/.forge/project-settings.json. They are the VALUES; the published
|
|
18
|
+
* `project_config` is the SCHEMA, and the two must not share a field --
|
|
19
|
+
* before this slice `resolveGates` wrote the values over the schema, so a
|
|
20
|
+
* consumer asking the definition for the field names got a project's
|
|
21
|
+
* answers instead.
|
|
22
|
+
*
|
|
23
|
+
* WHAT THIS MODULE REFUSES TO GUESS. The evaluator `heuristics` asks that
|
|
24
|
+
* every active rule have "an instrument, a severity and a checker". The
|
|
25
|
+
* data publishes the first two per rule. The third it does not: 29 of the
|
|
26
|
+
* 45 rules say `checker_declared_by: adapter`, and the adapter table is a
|
|
27
|
+
* POINTER -- `adapters.annex_columns.note` says the text of every cell
|
|
28
|
+
* lives only in the annex, and the pointer covers 6 rules of 45. The other
|
|
29
|
+
* 16 say `owner`, and the field name the owner would write it in is not
|
|
30
|
+
* published either (`adapters.measured_by.illustrative: true`, with the
|
|
31
|
+
* warning that an installation taking those names as defaults "is
|
|
32
|
+
* inventing layer 2"). So the checker resolves to `not_published` and the
|
|
33
|
+
* gate reports it as something it COULD NOT VERIFY. Not green, not red:
|
|
34
|
+
* named. Inventing the cell here would be the same defect this file was
|
|
35
|
+
* written to delete.
|
|
36
|
+
*
|
|
37
|
+
* ASCII-only.
|
|
38
|
+
*/
|
|
39
|
+
import fs from 'node:fs/promises';
|
|
40
|
+
import path from 'node:path';
|
|
41
|
+
import { readManualCases } from './manual_cases.js';
|
|
42
|
+
export * from './scoreboard.js';
|
|
43
|
+
export function projectSettingsPath(root) {
|
|
44
|
+
return path.join(root, '.forge', 'project-settings.json');
|
|
45
|
+
}
|
|
46
|
+
export async function readProjectSettings(root) {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(await fs.readFile(projectSettingsPath(root), 'utf8'));
|
|
49
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
50
|
+
return parsed;
|
|
51
|
+
}
|
|
52
|
+
catch { /* no declarations: that is a gap the report names, not an error */ }
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
function isRec(v) {
|
|
56
|
+
return v != null && typeof v === 'object' && !Array.isArray(v);
|
|
57
|
+
}
|
|
58
|
+
const RECORD_FIELDS = ['rule', 'reason', 'authorized_by', 'review_date'];
|
|
59
|
+
/** A switch-off record, or the list of what is missing from it. The method
|
|
60
|
+
* is explicit that an incomplete record is not a lenient switch-off: it is
|
|
61
|
+
* red, and it makes the whole ruleset invalid (D-046, D-047). */
|
|
62
|
+
export function switchOffProblems(entry, index) {
|
|
63
|
+
if (!isRec(entry)) {
|
|
64
|
+
return { problems: ['switched_off[' + index + '] no es un registro: ' + JSON.stringify(entry ?? null)] };
|
|
65
|
+
}
|
|
66
|
+
const rule = typeof entry['rule'] === 'string' ? entry['rule'] : undefined;
|
|
67
|
+
const problems = [];
|
|
68
|
+
for (const f of RECORD_FIELDS) {
|
|
69
|
+
const v = entry[f];
|
|
70
|
+
if (typeof v !== 'string' || v.trim() === '') {
|
|
71
|
+
problems.push('apagada sin un registro completo: ' + (rule ?? 'switched_off[' + index + ']')
|
|
72
|
+
+ ' no trae ' + f);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { ...(rule ? { rule } : {}), problems };
|
|
76
|
+
}
|
|
77
|
+
function severityOf(rule, realUsers, declaredThresholdIds) {
|
|
78
|
+
const base = String(rule['initial_severity'] ?? 'records');
|
|
79
|
+
const esc = rule['escalates_to'];
|
|
80
|
+
/* CQ-18 escalates to `blocks` when the product has users outside the
|
|
81
|
+
* organisation, and the method says the escalation "requires a
|
|
82
|
+
* declaration by the owner, through the same mechanism as any threshold"
|
|
83
|
+
* (D-048). So real_users alone does not escalate it: without the owner's
|
|
84
|
+
* declaration it stays where the catalogue put it, and the report says
|
|
85
|
+
* the escalation is pending a declaration. */
|
|
86
|
+
if (isRec(esc) && realUsers && declaredThresholdIds.has(String(rule['id']))) {
|
|
87
|
+
return { severity: String(esc['severity'] ?? base), source: 'escalated' };
|
|
88
|
+
}
|
|
89
|
+
return { severity: base, source: 'catalog' };
|
|
90
|
+
}
|
|
91
|
+
function thresholdsOf(adapters, settings) {
|
|
92
|
+
const declared = isRec(adapters) && Array.isArray(adapters['thresholds'])
|
|
93
|
+
? adapters['thresholds']
|
|
94
|
+
: [];
|
|
95
|
+
const own = settings?.thresholds ?? {};
|
|
96
|
+
return declared.filter(isRec).map((t) => {
|
|
97
|
+
const id = String(t['id']);
|
|
98
|
+
const rule = String(t['rule'] ?? '');
|
|
99
|
+
if (Object.prototype.hasOwnProperty.call(own, id) && own[id] != null) {
|
|
100
|
+
return { id, rule, value: own[id], declared: true, source: 'project' };
|
|
101
|
+
}
|
|
102
|
+
if (t['declared'] === true && t['value'] != null) {
|
|
103
|
+
return { id, rule, value: t['value'], declared: true, source: 'annex' };
|
|
104
|
+
}
|
|
105
|
+
/* Not declared anywhere: reported as undeclared. No approximating, no
|
|
106
|
+
* silent inheriting and no inventing (adapters.undeclared_threshold). */
|
|
107
|
+
return { id, rule, value: null, declared: false, source: 'undeclared' };
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
export async function resolveRuleset(opts) {
|
|
111
|
+
const settings = opts.settings ?? await readProjectSettings(opts.projectRoot);
|
|
112
|
+
const h = isRec(opts.gates.heuristics) ? opts.gates.heuristics : {};
|
|
113
|
+
const publishedRules = Array.isArray(h['rules']) ? h['rules'].filter(isRec) : [];
|
|
114
|
+
const scoreboard = isRec(h['scoreboard']) ? h['scoreboard'] : {};
|
|
115
|
+
const outsideNamed = new Set((Array.isArray(scoreboard['human_outside_the_percentage'])
|
|
116
|
+
? scoreboard['human_outside_the_percentage']
|
|
117
|
+
: []).filter((x) => typeof x === 'string' && /^[A-Z]{2}-\d\d$/.test(x)));
|
|
118
|
+
const realUsers = settings?.real_users === true;
|
|
119
|
+
/* ---- the switch-off list, and whether it is a valid ruleset ---- */
|
|
120
|
+
const problems = [];
|
|
121
|
+
const invalidating = [];
|
|
122
|
+
const offRecords = new Map();
|
|
123
|
+
const offIds = new Set();
|
|
124
|
+
const rawOff = Array.isArray(settings?.switched_off) ? settings?.switched_off : [];
|
|
125
|
+
rawOff.forEach((entry, i) => {
|
|
126
|
+
const r = switchOffProblems(entry, i);
|
|
127
|
+
if (r.rule)
|
|
128
|
+
offIds.add(r.rule);
|
|
129
|
+
if (r.problems.length) {
|
|
130
|
+
problems.push(...r.problems);
|
|
131
|
+
invalidating.push(...r.problems);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
offRecords.set(r.rule, entry);
|
|
135
|
+
});
|
|
136
|
+
/* ---- the group switches ---- */
|
|
137
|
+
const groupOff = [];
|
|
138
|
+
for (const [group, sw] of Object.entries(settings?.switches ?? {})) {
|
|
139
|
+
if (sw === true)
|
|
140
|
+
continue;
|
|
141
|
+
if (!isRec(sw)) {
|
|
142
|
+
const p = 'switches.' + group + ' no es ni true ni un registro de apagado';
|
|
143
|
+
problems.push(p);
|
|
144
|
+
invalidating.push(p);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const missing = ['reason', 'authorized_by', 'review_date']
|
|
148
|
+
.filter((f) => typeof sw[f] !== 'string' || String(sw[f]).trim() === '');
|
|
149
|
+
if (missing.length) {
|
|
150
|
+
const p = 'el grupo ' + group + ' esta apagado sin un registro completo: falta ' + missing.join(', ');
|
|
151
|
+
problems.push(p);
|
|
152
|
+
invalidating.push(p);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
groupOff.push(group);
|
|
156
|
+
}
|
|
157
|
+
const thresholds = thresholdsOf(opts.gates.adapters, settings);
|
|
158
|
+
/* The escalation of a severity travels "through the same mechanism as any
|
|
159
|
+
* threshold" (D-048), so the owner declares it as a `thresholds` entry
|
|
160
|
+
* keyed by the rule id. The method publishes no threshold id for it, which
|
|
161
|
+
* is why this is the rule id and not one of the five published ones. */
|
|
162
|
+
const ownerDeclared = new Set(Object.keys(settings?.thresholds ?? {}));
|
|
163
|
+
const couldNotVerify = [];
|
|
164
|
+
const rules = publishedRules.map((raw) => {
|
|
165
|
+
const id = String(raw['id']);
|
|
166
|
+
const group = String(raw['group'] ?? '');
|
|
167
|
+
const instrument = Array.isArray(raw['instrument']) ? raw['instrument'].map(String) : [];
|
|
168
|
+
const humanRed = raw['human_red'] === true;
|
|
169
|
+
const { severity, source } = severityOf(raw, realUsers, ownerDeclared);
|
|
170
|
+
const appliesIf = typeof raw['applies_if'] === 'string' ? raw['applies_if'] : undefined;
|
|
171
|
+
let state = 'active';
|
|
172
|
+
let why;
|
|
173
|
+
if (groupOff.includes(group)) {
|
|
174
|
+
state = 'group_off';
|
|
175
|
+
why = 'the group ' + group + ' is switched off, and a switched-off group is never read as fulfilled';
|
|
176
|
+
}
|
|
177
|
+
else if (offRecords.has(id)) {
|
|
178
|
+
const rec = offRecords.get(id);
|
|
179
|
+
state = 'switched_off';
|
|
180
|
+
why = 'switched off by ' + rec.authorized_by + ', review ' + rec.review_date + ': ' + rec.reason;
|
|
181
|
+
}
|
|
182
|
+
else if (appliesIf === 'real_users' && !realUsers) {
|
|
183
|
+
state = 'out_of_scope';
|
|
184
|
+
why = 'applies_if real_users, and this project declares real_users=false: that is scope, not an exception';
|
|
185
|
+
}
|
|
186
|
+
/* `checker_declared_by` says WHO declares what measures the rule; the
|
|
187
|
+
* data does not carry the cell itself (see the header). */
|
|
188
|
+
const checker = 'not_published';
|
|
189
|
+
const outside = humanRed || instrument.includes('manual_case') || outsideNamed.has(id);
|
|
190
|
+
return {
|
|
191
|
+
id, group,
|
|
192
|
+
family: String(raw['family'] ?? ''),
|
|
193
|
+
instrument,
|
|
194
|
+
severity, severity_source: source,
|
|
195
|
+
state,
|
|
196
|
+
human_red: humanRed,
|
|
197
|
+
in_percentage: state === 'active' && !outside,
|
|
198
|
+
passing: false,
|
|
199
|
+
measured: false,
|
|
200
|
+
checker,
|
|
201
|
+
...(offRecords.has(id) ? { record: offRecords.get(id) } : {}),
|
|
202
|
+
...(why ? { why } : {}),
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
for (const r of publishedRules) {
|
|
206
|
+
if (isRec(r['escalates_to']) && realUsers && !ownerDeclared.has(String(r['id']))) {
|
|
207
|
+
couldNotVerify.push('la regla ' + String(r['id']) + ' escala su severidad cuando el producto tiene'
|
|
208
|
+
+ ' usuarios fuera de la organizacion, y la escalada necesita que el dueno la declare por el mismo'
|
|
209
|
+
+ ' mecanismo que cualquier umbral -- la definicion en vigor no publica ningun id de umbral para'
|
|
210
|
+
+ ' ella, asi que esta instalacion no distingue una escalada pendiente de una declinada.');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const activeNeedingChecker = rules.filter((r) => r.state === 'active').length;
|
|
214
|
+
if (activeNeedingChecker > 0) {
|
|
215
|
+
couldNotVerify.push('con que se mide cada regla: la definicion en vigor no publica la celda del'
|
|
216
|
+
+ ' adaptador (adapters.annex_columns es un puntero al anexo y cubre 6 reglas de 45) ni publica el'
|
|
217
|
+
+ ' nombre del campo donde el dueno la declararia (adapters.measured_by.illustrative=true). '
|
|
218
|
+
+ activeNeedingChecker + ' reglas activas no tienen entonces NINGUN medidor alcanzable, y esta'
|
|
219
|
+
+ ' instalacion lo reporta en lugar de inventar uno.');
|
|
220
|
+
}
|
|
221
|
+
if (!settings) {
|
|
222
|
+
problems.push('este proyecto no declara nada: falta .forge/project-settings.json, asi que ningun grupo'
|
|
223
|
+
+ ' esta declarado activo y no hay umbral, adaptador ni version de herramienta en el registro');
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
for (const g of ['code_quality', 'usability']) {
|
|
227
|
+
if (!Object.prototype.hasOwnProperty.call(settings.switches ?? {}, g)) {
|
|
228
|
+
problems.push('el grupo ' + g + ' no esta declarado en switches: el metodo pide una entrada por grupo');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/* A ruleset is INVALID only when something was switched off without its
|
|
233
|
+
* record (D-046/D-047). A missing declaration is a red verdict with the
|
|
234
|
+
* figures still emitted; an invalid ruleset emits no figures at all. */
|
|
235
|
+
return {
|
|
236
|
+
catalog_version: String(opts.gates.catalog_version ?? opts.gates.version ?? 'unknown'),
|
|
237
|
+
adapter: settings?.frontend ?? null,
|
|
238
|
+
tools: settings?.tools ?? {},
|
|
239
|
+
real_users: realUsers,
|
|
240
|
+
rules,
|
|
241
|
+
thresholds,
|
|
242
|
+
group_off: groupOff,
|
|
243
|
+
validity: { ok: invalidating.length === 0, problems, invalidating },
|
|
244
|
+
could_not_verify: couldNotVerify,
|
|
245
|
+
manual_cases: await readManualCases(opts.projectRoot),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=heuristics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heuristics.js","sourceRoot":"","sources":["../../src/urazuke/heuristics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type ManualVerdict = 'passed' | 'failed';
|
|
2
|
+
export interface ManualCase {
|
|
3
|
+
/** The rule id the verdict is about (CQ-12, UX-22, ...). */
|
|
4
|
+
rule: string;
|
|
5
|
+
verdict: ManualVerdict;
|
|
6
|
+
/** The person who judged it. Never a tool, never "the agent". */
|
|
7
|
+
judged_by: string;
|
|
8
|
+
/** ISO date (YYYY-MM-DD) or a full ISO timestamp. */
|
|
9
|
+
at: string;
|
|
10
|
+
/** The profile the case was run under, when the project has profiles.
|
|
11
|
+
* `manual_cases_per_profile` needs one run per affected profile; this
|
|
12
|
+
* field is what will let that evaluator be measured without a migration. */
|
|
13
|
+
profile?: string;
|
|
14
|
+
note?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ManualCaseLedger {
|
|
17
|
+
version: 1;
|
|
18
|
+
cases: ManualCase[];
|
|
19
|
+
}
|
|
20
|
+
export declare function manualCasesPath(root: string): string;
|
|
21
|
+
export declare function readManualCases(root: string): Promise<ManualCaseLedger>;
|
|
22
|
+
/** A stored entry only counts when it still carries the four things that
|
|
23
|
+
* make it a verdict. A hand-edited ledger that lost a name loses the
|
|
24
|
+
* verdict with it -- it does not inherit a green. */
|
|
25
|
+
export declare function isVerdict(c: unknown): c is ManualCase;
|
|
26
|
+
export declare function recordManualCase(root: string, c: ManualCase): Promise<ManualCaseLedger>;
|
|
27
|
+
/** The verdict in force for a rule: the LAST recorded one. Undefined means
|
|
28
|
+
* nobody signed, which is neither a pass nor a fail and must never be
|
|
29
|
+
* rendered as either. */
|
|
30
|
+
export declare function verdictFor(led: ManualCaseLedger, rule: string, profile?: string): ManualCase | undefined;
|
|
31
|
+
//# sourceMappingURL=manual_cases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manual_cases.d.ts","sourceRoot":"","sources":["../../src/urazuke/manual_cases.ts"],"names":[],"mappings":"AA6BA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,aAAa,CAAC;IACvB,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,EAAE,EAAE,MAAM,CAAC;IACX;;iFAE6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAQ7E;AAED;;sDAEsD;AACtD,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,UAAU,CAOrD;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyB7F;AAED;;0BAE0B;AAC1B,wBAAgB,UAAU,CAAC,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAGxG"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE HUMAN VERDICT ON RECORD (Annex H, pieza 5).
|
|
3
|
+
*
|
|
4
|
+
* Four of the forty-five rules carry `human_red: true` in the published
|
|
5
|
+
* data -- CQ-12, CQ-14, UX-22 and UX-27 as of catalog 2026.10.6 -- and the
|
|
6
|
+
* method's `manual_case` instrument says what such a verdict IS: "passed or
|
|
7
|
+
* failed, with a name and a date", judged by a person. The list is read
|
|
8
|
+
* from the definition in force, never from this file: a rule that gains
|
|
9
|
+
* `human_red` in a later catalogue is picked up without an install.
|
|
10
|
+
*
|
|
11
|
+
* Why a ledger and not a boolean. Before this module the `owner` state
|
|
12
|
+
* existed in the gate and signed nothing: a step with nothing mechanical to
|
|
13
|
+
* look at came out as "the owner verifies it", and the close gate let it
|
|
14
|
+
* through (`orchestrator.ts` treated only `red` and `unmeasured` as
|
|
15
|
+
* blocking). So the strongest claim in the method -- a person looked at
|
|
16
|
+
* this and said yes -- was the one claim with no evidence behind it. The
|
|
17
|
+
* ledger makes it a record with a name and a date, or it makes it absent.
|
|
18
|
+
*
|
|
19
|
+
* What is refused on write. A verdict with an empty `judged_by` or an empty
|
|
20
|
+
* `at` is not a verdict with a missing field, it is not a verdict: an
|
|
21
|
+
* anonymous or undated signature is exactly the "green without having
|
|
22
|
+
* looked" this whole slice exists to remove. `recordManualCase` throws
|
|
23
|
+
* instead of storing it.
|
|
24
|
+
*
|
|
25
|
+
* ASCII-only.
|
|
26
|
+
*/
|
|
27
|
+
import fs from 'node:fs/promises';
|
|
28
|
+
import path from 'node:path';
|
|
29
|
+
export function manualCasesPath(root) {
|
|
30
|
+
return path.join(root, '.forge', 'urazuke-manual-cases.json');
|
|
31
|
+
}
|
|
32
|
+
export async function readManualCases(root) {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(await fs.readFile(manualCasesPath(root), 'utf8'));
|
|
35
|
+
if (parsed && parsed.version === 1 && Array.isArray(parsed.cases)) {
|
|
36
|
+
return { version: 1, cases: parsed.cases.filter(isVerdict) };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch { /* no ledger yet: that is "nobody signed", not an error */ }
|
|
40
|
+
return { version: 1, cases: [] };
|
|
41
|
+
}
|
|
42
|
+
/** A stored entry only counts when it still carries the four things that
|
|
43
|
+
* make it a verdict. A hand-edited ledger that lost a name loses the
|
|
44
|
+
* verdict with it -- it does not inherit a green. */
|
|
45
|
+
export function isVerdict(c) {
|
|
46
|
+
if (c == null || typeof c !== 'object')
|
|
47
|
+
return false;
|
|
48
|
+
const r = c;
|
|
49
|
+
return typeof r['rule'] === 'string' && String(r['rule']).trim() !== ''
|
|
50
|
+
&& (r['verdict'] === 'passed' || r['verdict'] === 'failed')
|
|
51
|
+
&& typeof r['judged_by'] === 'string' && String(r['judged_by']).trim() !== ''
|
|
52
|
+
&& typeof r['at'] === 'string' && String(r['at']).trim() !== '';
|
|
53
|
+
}
|
|
54
|
+
export async function recordManualCase(root, c) {
|
|
55
|
+
if (!c || typeof c.rule !== 'string' || c.rule.trim() === '') {
|
|
56
|
+
throw new Error('a manual case needs the rule it judges');
|
|
57
|
+
}
|
|
58
|
+
if (c.verdict !== 'passed' && c.verdict !== 'failed') {
|
|
59
|
+
throw new Error('verdict has to be passed or failed: ' + String(c.verdict));
|
|
60
|
+
}
|
|
61
|
+
if (typeof c.judged_by !== 'string' || c.judged_by.trim() === '') {
|
|
62
|
+
throw new Error('judged_by is empty: an anonymous verdict is not a verdict (rule ' + c.rule + ')');
|
|
63
|
+
}
|
|
64
|
+
if (typeof c.at !== 'string' || c.at.trim() === '') {
|
|
65
|
+
throw new Error('at is empty: an undated verdict is not a verdict (rule ' + c.rule + ')');
|
|
66
|
+
}
|
|
67
|
+
const led = await readManualCases(root);
|
|
68
|
+
/* Append-only by design: the previous verdict stays visible, and the
|
|
69
|
+
* latest one for a (rule, profile) pair is the one that counts. The
|
|
70
|
+
* history is how a reviewer sees a red that turned green and when. */
|
|
71
|
+
led.cases.push({
|
|
72
|
+
rule: c.rule, verdict: c.verdict, judged_by: c.judged_by, at: c.at,
|
|
73
|
+
...(c.profile ? { profile: c.profile } : {}),
|
|
74
|
+
...(c.note ? { note: c.note } : {}),
|
|
75
|
+
});
|
|
76
|
+
await fs.mkdir(path.dirname(manualCasesPath(root)), { recursive: true });
|
|
77
|
+
await fs.writeFile(manualCasesPath(root), JSON.stringify(led, null, 2) + '\n', 'utf8');
|
|
78
|
+
return led;
|
|
79
|
+
}
|
|
80
|
+
/** The verdict in force for a rule: the LAST recorded one. Undefined means
|
|
81
|
+
* nobody signed, which is neither a pass nor a fail and must never be
|
|
82
|
+
* rendered as either. */
|
|
83
|
+
export function verdictFor(led, rule, profile) {
|
|
84
|
+
const matching = led.cases.filter((c) => c.rule === rule && (profile == null || c.profile === profile));
|
|
85
|
+
return matching.length ? matching[matching.length - 1] : undefined;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=manual_cases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manual_cases.js","sourceRoot":"","sources":["../../src/urazuke/manual_cases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAwB7B,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,2BAA2B,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY;IAChD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAqB,CAAC;QAChG,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAClE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,0DAA0D,CAAC,CAAC;IACtE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AACnC,CAAC;AAED;;sDAEsD;AACtD,MAAM,UAAU,SAAS,CAAC,CAAU;IAClC,IAAI,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACrD,MAAM,CAAC,GAAG,CAA4B,CAAC;IACvC,OAAO,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE;WAClE,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC;WACxD,OAAO,CAAC,CAAC,WAAW,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE;WAC1E,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;AACpE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAY,EAAE,CAAa;IAChE,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,sCAAsC,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,kEAAkE,GAAG,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;IACrG,CAAC;IACD,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,yDAAyD,GAAG,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;IAC5F,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;IACxC;;0EAEsE;IACtE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;QACb,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE;QAClE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5C,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpC,CAAC,CAAC;IACH,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzE,MAAM,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;IACvF,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;0BAE0B;AAC1B,MAAM,UAAU,UAAU,CAAC,GAAqB,EAAE,IAAY,EAAE,OAAgB;IAC9E,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC;IACxG,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACrE,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROJECT CONFIGURATION: validation and semantics.
|
|
3
|
+
*
|
|
4
|
+
* A project's .forge/project-config.json can declare switches to
|
|
5
|
+
* turn off rules of the method. Each off-switch requires a 4-field
|
|
6
|
+
* audit record (rule, reason, authorized_by, review_date), and
|
|
7
|
+
* an evaluator can only read a rule as off if the record is complete.
|
|
8
|
+
*
|
|
9
|
+
* ASCII-only.
|
|
10
|
+
*/
|
|
11
|
+
export interface SwitchOffRecord {
|
|
12
|
+
rule: string;
|
|
13
|
+
reason: string;
|
|
14
|
+
authorized_by: string;
|
|
15
|
+
review_date: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ValidationResult {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
invalid: string[];
|
|
20
|
+
problems: string[];
|
|
21
|
+
}
|
|
22
|
+
export declare function isSwitchOffValid(record: unknown): record is SwitchOffRecord;
|
|
23
|
+
export declare function validateSwitchOffRecord(records: unknown): ValidationResult;
|
|
24
|
+
export declare function isRuleSwitchedOff(rule: string, switched_off: string[] | undefined, switch_off_record: unknown): boolean;
|
|
25
|
+
//# sourceMappingURL=project_config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project_config.d.ts","sourceRoot":"","sources":["../../src/urazuke/project_config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAQD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,eAAe,CAM3E;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,gBAAgB,CAkC1E;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EAAE,GAAG,SAAS,EAClC,iBAAiB,EAAE,OAAO,GACzB,OAAO,CAQT"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROJECT CONFIGURATION: validation and semantics.
|
|
3
|
+
*
|
|
4
|
+
* A project's .forge/project-config.json can declare switches to
|
|
5
|
+
* turn off rules of the method. Each off-switch requires a 4-field
|
|
6
|
+
* audit record (rule, reason, authorized_by, review_date), and
|
|
7
|
+
* an evaluator can only read a rule as off if the record is complete.
|
|
8
|
+
*
|
|
9
|
+
* ASCII-only.
|
|
10
|
+
*/
|
|
11
|
+
const REQUIRED_FIELDS = ['rule', 'reason', 'authorized_by', 'review_date'];
|
|
12
|
+
function isRecord(v) {
|
|
13
|
+
return v != null && typeof v === 'object' && !Array.isArray(v);
|
|
14
|
+
}
|
|
15
|
+
export function isSwitchOffValid(record) {
|
|
16
|
+
if (!isRecord(record))
|
|
17
|
+
return false;
|
|
18
|
+
for (const field of REQUIRED_FIELDS) {
|
|
19
|
+
if (typeof record[field] !== 'string')
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
export function validateSwitchOffRecord(records) {
|
|
25
|
+
const invalid = [];
|
|
26
|
+
const problems = [];
|
|
27
|
+
const input = Array.isArray(records) ? records : [];
|
|
28
|
+
for (const rec of input) {
|
|
29
|
+
if (!isRecord(rec)) {
|
|
30
|
+
/* Reading `.rule` off this is what used to throw: a null left behind
|
|
31
|
+
* by a hand-edited config crashed the validator instead of being
|
|
32
|
+
* reported as the invalid entry it is. */
|
|
33
|
+
invalid.push('?');
|
|
34
|
+
problems.push('record is not an object: ' + JSON.stringify(rec ?? null));
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const rule = rec.rule;
|
|
38
|
+
if (typeof rule !== 'string') {
|
|
39
|
+
invalid.push('?');
|
|
40
|
+
problems.push('rule is not a string');
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
for (const field of REQUIRED_FIELDS) {
|
|
44
|
+
if (typeof rec[field] !== 'string') {
|
|
45
|
+
invalid.push(rule);
|
|
46
|
+
problems.push(`rule ${rule}: ${field} is missing or not a string`);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
ok: invalid.length === 0,
|
|
53
|
+
invalid,
|
|
54
|
+
problems,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function isRuleSwitchedOff(rule, switched_off, switch_off_record) {
|
|
58
|
+
const list = switched_off ?? [];
|
|
59
|
+
if (!list.includes(rule))
|
|
60
|
+
return false;
|
|
61
|
+
const records = Array.isArray(switch_off_record) ? switch_off_record : [];
|
|
62
|
+
const recordForRule = records.find((r) => isRecord(r) && r.rule === rule);
|
|
63
|
+
return isSwitchOffValid(recordForRule);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=project_config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project_config.js","sourceRoot":"","sources":["../../src/urazuke/project_config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAeH,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;AAE3E,SAAS,QAAQ,CAAC,CAAU;IAC1B,OAAO,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAe;IAC9C,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACpC,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;QACpC,IAAI,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IACtD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,OAAgB;IACtD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAEpD,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACnB;;sDAE0C;YAC1C,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClB,QAAQ,CAAC,IAAI,CAAC,2BAA2B,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC;YACzE,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QACtB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClB,QAAQ,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;YACtC,SAAS;QACX,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;YACpC,IAAI,OAAO,GAAG,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACnC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACnB,QAAQ,CAAC,IAAI,CAAC,QAAQ,IAAI,KAAK,KAAK,6BAA6B,CAAC,CAAC;gBACnE,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;QACxB,OAAO;QACP,QAAQ;KACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,IAAY,EACZ,YAAkC,EAClC,iBAA0B;IAE1B,MAAM,IAAI,GAAG,YAAY,IAAI,EAAE,CAAC;IAChC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAEvC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAE1E,OAAO,gBAAgB,CAAC,aAAa,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -2,9 +2,6 @@ import { type StackAdapter } from './stacks.js';
|
|
|
2
2
|
export interface QualityLimits {
|
|
3
3
|
max_file_lines: number;
|
|
4
4
|
max_function_lines: number;
|
|
5
|
-
max_complexity: number;
|
|
6
|
-
/** Lines in a duplicated block to count it. */
|
|
7
|
-
dup_block_lines: number;
|
|
8
5
|
}
|
|
9
6
|
export declare const DEFAULT_LIMITS: QualityLimits;
|
|
10
7
|
export interface Violation {
|
|
@@ -20,8 +17,6 @@ export interface NativeMeasure {
|
|
|
20
17
|
violations: Violation[];
|
|
21
18
|
max_file_lines: number;
|
|
22
19
|
max_function_lines: number;
|
|
23
|
-
max_complexity: number;
|
|
24
|
-
duplicated_blocks: number;
|
|
25
20
|
}
|
|
26
21
|
interface FnSpan {
|
|
27
22
|
name: string;
|
|
@@ -39,14 +34,12 @@ export interface Ratchet {
|
|
|
39
34
|
coverage_pct?: number;
|
|
40
35
|
mutation_score?: number;
|
|
41
36
|
violations?: number;
|
|
42
|
-
duplicated_blocks?: number;
|
|
43
37
|
updated_at: number;
|
|
44
38
|
history: Array<{
|
|
45
39
|
at: number;
|
|
46
40
|
coverage_pct?: number;
|
|
47
41
|
mutation_score?: number;
|
|
48
42
|
violations: number;
|
|
49
|
-
duplicated_blocks: number;
|
|
50
43
|
}>;
|
|
51
44
|
}
|
|
52
45
|
export declare function ratchetPath(root: string): string;
|
|
@@ -63,7 +56,6 @@ export declare function applyRatchet(prev: Ratchet, m: {
|
|
|
63
56
|
coverage_pct?: number;
|
|
64
57
|
mutation_score?: number;
|
|
65
58
|
violations: number;
|
|
66
|
-
duplicated_blocks: number;
|
|
67
59
|
}, now?: number): RatchetVerdict;
|
|
68
60
|
export interface QualityCheckResult {
|
|
69
61
|
stack: StackAdapter;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quality.d.ts","sourceRoot":"","sources":["../../src/urazuke/quality.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAmD,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAEjG,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"quality.d.ts","sourceRoot":"","sources":["../../src/urazuke/quality.ts"],"names":[],"mappings":"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"}
|
package/dist/urazuke/quality.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import fs from 'node:fs/promises';
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import { detectStack, readCoveragePct, readMutationScore } from './stacks.js';
|
|
21
|
-
export const DEFAULT_LIMITS = { max_file_lines: 300, max_function_lines: 40
|
|
21
|
+
export const DEFAULT_LIMITS = { max_file_lines: 300, max_function_lines: 40 };
|
|
22
22
|
/* ---------------- scanning ---------------- */
|
|
23
23
|
async function walk(root, a) {
|
|
24
24
|
const out = [];
|
|
@@ -121,27 +121,9 @@ export async function measureNative(root, a, limits = DEFAULT_LIMITS) {
|
|
|
121
121
|
maxCx = Math.max(maxCx, fn.complexity);
|
|
122
122
|
if (len > limits.max_function_lines)
|
|
123
123
|
violations.push({ kind: 'function_lines', where: rel + ':' + fn.start + ' ' + fn.name, value: len, limit: limits.max_function_lines });
|
|
124
|
-
if (fn.complexity > limits.max_complexity)
|
|
125
|
-
violations.push({ kind: 'complexity', where: rel + ':' + fn.start + ' ' + fn.name, value: fn.complexity, limit: limits.max_complexity });
|
|
126
|
-
}
|
|
127
|
-
const norm = code.map(normalise).filter((l) => l.length > 12);
|
|
128
|
-
for (let i = 0; i + limits.dup_block_lines <= norm.length; i++) {
|
|
129
|
-
const key = norm.slice(i, i + limits.dup_block_lines).join('\n');
|
|
130
|
-
const arr = blocks.get(key) ?? [];
|
|
131
|
-
arr.push(rel + ':' + (i + 1));
|
|
132
|
-
blocks.set(key, arr);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
let dup = 0;
|
|
136
|
-
for (const [, where] of blocks) {
|
|
137
|
-
const distinctFiles = new Set(where.map((w) => w.split(':')[0]));
|
|
138
|
-
if (where.length > 1 && (distinctFiles.size > 1 || where.length > 2)) {
|
|
139
|
-
dup += 1;
|
|
140
|
-
if (violations.filter((v) => v.kind === 'duplication').length < 50)
|
|
141
|
-
violations.push({ kind: 'duplication', where: where.slice(0, 3).join(' = '), value: where.length, limit: 1 });
|
|
142
124
|
}
|
|
143
125
|
}
|
|
144
|
-
return { files: files.length, lines, functions, violations, max_file_lines: maxFile, max_function_lines: maxFn
|
|
126
|
+
return { files: files.length, lines, functions, violations, max_file_lines: maxFile, max_function_lines: maxFn };
|
|
145
127
|
}
|
|
146
128
|
export function ratchetPath(root) { return path.join(root, '.forge', 'quality-ratchet.json'); }
|
|
147
129
|
export async function readRatchet(root) {
|
|
@@ -167,16 +149,13 @@ export function applyRatchet(prev, m, now = Date.now()) {
|
|
|
167
149
|
reasons.push('mutation score dropped ' + prev.mutation_score + ' -> ' + m.mutation_score);
|
|
168
150
|
if (prev.violations != null && m.violations > prev.violations)
|
|
169
151
|
reasons.push('violations rose ' + prev.violations + ' -> ' + m.violations);
|
|
170
|
-
if (prev.duplicated_blocks != null && m.duplicated_blocks > prev.duplicated_blocks)
|
|
171
|
-
reasons.push('duplicated blocks rose ' + prev.duplicated_blocks + ' -> ' + m.duplicated_blocks);
|
|
172
152
|
const ok = reasons.length === 0;
|
|
173
153
|
const next = {
|
|
174
154
|
version: 1, updated_at: now,
|
|
175
155
|
coverage_pct: ok ? Math.max(prev.coverage_pct ?? -1, m.coverage_pct ?? -1) : prev.coverage_pct,
|
|
176
156
|
mutation_score: ok ? Math.max(prev.mutation_score ?? -1, m.mutation_score ?? -1) : prev.mutation_score,
|
|
177
157
|
violations: ok ? Math.min(prev.violations ?? Infinity, m.violations) : prev.violations,
|
|
178
|
-
|
|
179
|
-
history: [...prev.history.slice(-199), { at: now, ...(m.coverage_pct != null ? { coverage_pct: m.coverage_pct } : {}), ...(m.mutation_score != null ? { mutation_score: m.mutation_score } : {}), violations: m.violations, duplicated_blocks: m.duplicated_blocks }],
|
|
158
|
+
history: [...prev.history.slice(-199), { at: now, ...(m.coverage_pct != null ? { coverage_pct: m.coverage_pct } : {}), ...(m.mutation_score != null ? { mutation_score: m.mutation_score } : {}), violations: m.violations }],
|
|
180
159
|
};
|
|
181
160
|
if (next.coverage_pct === -1)
|
|
182
161
|
delete next.coverage_pct;
|
|
@@ -184,8 +163,6 @@ export function applyRatchet(prev, m, now = Date.now()) {
|
|
|
184
163
|
delete next.mutation_score;
|
|
185
164
|
if (next.violations === Infinity)
|
|
186
165
|
delete next.violations;
|
|
187
|
-
if (next.duplicated_blocks === Infinity)
|
|
188
|
-
delete next.duplicated_blocks;
|
|
189
166
|
return { ok, reasons, next };
|
|
190
167
|
}
|
|
191
168
|
export async function runQualityCheck(root, opts = {}) {
|
|
@@ -215,7 +192,7 @@ export async function runQualityCheck(root, opts = {}) {
|
|
|
215
192
|
mutation_score = (await readMutationScore(root, stack, r.output))?.score;
|
|
216
193
|
}
|
|
217
194
|
const prev = await readRatchet(root);
|
|
218
|
-
const ratchet = applyRatchet(prev, { ...(coverage_pct != null ? { coverage_pct } : {}), ...(mutation_score != null ? { mutation_score } : {}), violations: native.violations.length
|
|
195
|
+
const ratchet = applyRatchet(prev, { ...(coverage_pct != null ? { coverage_pct } : {}), ...(mutation_score != null ? { mutation_score } : {}), violations: native.violations.length });
|
|
219
196
|
const lintOk = lint.every((l) => l.exit_code === 0);
|
|
220
197
|
const ok = lintOk && ratchet.ok;
|
|
221
198
|
if (ok && opts.persist !== false)
|
|
@@ -247,8 +224,6 @@ export function renderCodeQuality(r, prevBody) {
|
|
|
247
224
|
'|---|---|---|---|',
|
|
248
225
|
'| Lineas por archivo (max) | ' + r.limits.max_file_lines + ' | ' + r.native.max_file_lines + ' | ' + (by('file_lines').length ? by('file_lines').length + ' archivos pasan el limite' : 'ok') + ' |',
|
|
249
226
|
'| Lineas por funcion (max) | ' + r.limits.max_function_lines + ' | ' + r.native.max_function_lines + ' | ' + (by('function_lines').length ? by('function_lines').length + ' funciones pasan el limite' : 'ok') + ' |',
|
|
250
|
-
'| Complejidad por funcion (max) | ' + r.limits.max_complexity + ' | ' + r.native.max_complexity + ' | ' + (by('complexity').length ? by('complexity').length + ' funciones pasan el limite' : 'ok') + ' |',
|
|
251
|
-
'| Bloques duplicados (' + r.limits.dup_block_lines + ' lineas) | 0 | ' + r.native.duplicated_blocks + ' | ' + (r.native.duplicated_blocks ? 'hay duplicacion' : 'ok') + ' |',
|
|
252
227
|
'| Cobertura de lineas (%) | piso ' + (r.ratchet.next.coverage_pct ?? '-') + ' | ' + (r.coverage_pct ?? '-') + ' | ' + (r.coverage_pct != null ? 'trinquete' : 'sin medir') + ' |',
|
|
253
228
|
'| Puntaje de mutacion (%) | piso ' + (r.ratchet.next.mutation_score ?? '-') + ' | ' + (r.mutation_score ?? '-') + ' | ' + (r.mutation_score != null ? 'trinquete' : 'sin medir') + ' |',
|
|
254
229
|
'| Lint / formato | 0 fallos | ' + r.lint.filter((l) => l.exit_code !== 0).length + ' fallos | ' + (r.lint.every((l) => l.exit_code === 0) ? 'ok' : 'rojo') + ' |',
|