skill-family-engineering-kit 0.1.2 → 0.2.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/CONTRIBUTING.md +13 -67
- package/README.md +26 -3
- package/SECURITY.md +4 -9
- package/THIRD_PARTY_NOTICES +22 -9
- package/docs/404.html +27 -0
- package/docs/architecture/index.html +84 -10
- package/docs/git-lifecycle/index.html +27 -0
- package/docs/help/index.html +27 -0
- package/docs/index.html +27 -0
- package/docs/integration/audit/failure-evidence/index.html +27 -0
- package/docs/integration/audit/independence/index.html +27 -0
- package/docs/integration/audit/index.html +29 -1
- package/docs/integration/audit/mutation-taxonomy/index.html +27 -0
- package/docs/integration/audit/version-compatibility/index.html +32 -2
- package/docs/migration/index.html +31 -4
- package/docs/public/status/index.html +858 -0
- package/docs/quickstart/index.html +27 -0
- package/docs/search/search_index.json +1 -1
- package/docs/setup/index.html +30 -2
- package/docs/sitemap.xml +4 -0
- package/package.json +5 -5
- package/src/check.mjs +211 -76
- package/src/cli.mjs +185 -4
- package/src/core-check.mjs +223 -0
- package/src/errors.mjs +16 -0
- package/src/host-drivers.mjs +24 -0
- package/src/host-profiles.mjs +51 -0
- package/src/host.mjs +108 -0
- package/src/index.mjs +35 -7
- package/src/report.mjs +335 -0
- package/src/skeleton.mjs +19 -79
- package/src/workspace.mjs +82 -0
package/docs/setup/index.html
CHANGED
|
@@ -504,6 +504,33 @@
|
|
|
504
504
|
|
|
505
505
|
|
|
506
506
|
|
|
507
|
+
<li class="md-nav__item">
|
|
508
|
+
<a href="../public/status/" class="md-nav__link">
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
<span class="md-ellipsis">
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
公开状态
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
</span>
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
</a>
|
|
524
|
+
</li>
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
|
|
507
534
|
<li class="md-nav__item">
|
|
508
535
|
<a href="../migration/" class="md-nav__link">
|
|
509
536
|
|
|
@@ -813,9 +840,9 @@ pnpm check
|
|
|
813
840
|
</code></pre>
|
|
814
841
|
<ul>
|
|
815
842
|
<li><code>pnpm synth</code> 再生成 projen 受管文件。受管文件只能通过修改 <code>.projenrc.js</code> 间接变更;手写源码、文档和 fixture 不会被 synth 覆盖。</li>
|
|
816
|
-
<li><code>pnpm check</code> 是当前统一根门禁:
|
|
843
|
+
<li><code>pnpm check</code> 是当前统一根门禁:11 个稳定门禁 ID 按固定顺序串联执行,任一步骤非 0 即整体失败;本仓没有任何 CI/CD workflow,全部门禁都在私有工作区内运行。下方区块由根 <code>package.json</code> 的 <code>scripts.check</code> 机械投影(<code>node scripts/docs/fact-check.mjs</code> 逐字核对,禁止手写清单再次滞后):</li>
|
|
817
844
|
</ul>
|
|
818
|
-
<!-- BEGIN SKILL-FAMILY-GATE-STAGES v2 (
|
|
845
|
+
<!-- BEGIN SKILL-FAMILY-GATE-STAGES v2 (11 stable gate IDs; derived from package.json scripts.check; verified by fact-check) -->
|
|
819
846
|
<ol>
|
|
820
847
|
<li>pnpm run check:structure</li>
|
|
821
848
|
<li>pnpm run check:packages</li>
|
|
@@ -827,6 +854,7 @@ pnpm check
|
|
|
827
854
|
<li>pnpm run check:release-artifacts</li>
|
|
828
855
|
<li>pnpm run check:projen</li>
|
|
829
856
|
<li>pnpm run check:integration</li>
|
|
857
|
+
<li>pnpm run check:artifacts</li>
|
|
830
858
|
</ol>
|
|
831
859
|
<!-- END SKILL-FAMILY-GATE-STAGES -->
|
|
832
860
|
|
package/docs/sitemap.xml
CHANGED
|
@@ -48,4 +48,8 @@
|
|
|
48
48
|
<loc>https://ifoohoo.github.io/skill-family-engineering-kit/migration/</loc>
|
|
49
49
|
<lastmod>1970-01-01</lastmod>
|
|
50
50
|
</url>
|
|
51
|
+
<url>
|
|
52
|
+
<loc>https://ifoohoo.github.io/skill-family-engineering-kit/public/status/</loc>
|
|
53
|
+
<lastmod>1970-01-01</lastmod>
|
|
54
|
+
</url>
|
|
51
55
|
</urlset>
|
package/package.json
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"url": "https://github.com/ifoohoo/skill-family-engineering-kit/issues"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"skill-family-contracts": "0.
|
|
12
|
-
"skill-family-harness-node": "0.
|
|
11
|
+
"skill-family-contracts": "0.2.0",
|
|
12
|
+
"skill-family-harness-node": "0.2.0"
|
|
13
13
|
},
|
|
14
14
|
"description": "Build-time scaffold, adoption planning, projection, and checks.",
|
|
15
15
|
"engines": {
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"url": "https://github.com/ifoohoo/skill-family-engineering-kit.git"
|
|
36
36
|
},
|
|
37
37
|
"type": "module",
|
|
38
|
-
"version": "0.
|
|
38
|
+
"version": "0.2.0",
|
|
39
39
|
"scripts": {
|
|
40
|
-
"check": "node --
|
|
41
|
-
"test": "node --
|
|
40
|
+
"check": "node --test",
|
|
41
|
+
"test": "node --test"
|
|
42
42
|
}
|
|
43
43
|
}
|
package/src/check.mjs
CHANGED
|
@@ -40,6 +40,15 @@ import {
|
|
|
40
40
|
* git — read-only Git pre-state (never a git write);
|
|
41
41
|
* identity — licensing and identity drift checks (FND-045).
|
|
42
42
|
*
|
|
43
|
+
* Every class is independently complete: it loads its own inputs and never
|
|
44
|
+
* consumes another class's cache, so a full run and a `--only <class>` run
|
|
45
|
+
* produce byte-identical findings for that class (the equivalence matrix in
|
|
46
|
+
* test/check-equivalence.test.mjs is the mechanical witness). Document
|
|
47
|
+
* loading distinguishes the four stable states missing / parse-failed /
|
|
48
|
+
* schema-invalid / incomplete, and class results distinguish selected /
|
|
49
|
+
* completed / findings — a class that never ran is never reported as having
|
|
50
|
+
* run.
|
|
51
|
+
*
|
|
43
52
|
* This module contains no write call of any kind. Mutation-looking flags
|
|
44
53
|
* (--fix, --apply, ...) are refused at intake by the CLI. Findings carry
|
|
45
54
|
* stable kinds and registered SFC codes; the exit code is the stable
|
|
@@ -50,48 +59,86 @@ const CHECK_CLASSES = Object.freeze(["contracts", "drift", "closure", "version",
|
|
|
50
59
|
|
|
51
60
|
export { CHECK_CLASSES };
|
|
52
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Stable document loading states every check class must distinguish:
|
|
64
|
+
* missing — the file does not exist;
|
|
65
|
+
* parse-failed — the bytes are not valid JSON;
|
|
66
|
+
* schema-invalid — the parsed document fails its registered schema;
|
|
67
|
+
* incomplete — parsed (and schema-checked where requested) but a fact
|
|
68
|
+
* the consuming class needs is absent;
|
|
69
|
+
* ok — every requested state passed.
|
|
70
|
+
*/
|
|
71
|
+
export const DOCUMENT_STATES = Object.freeze([
|
|
72
|
+
"missing",
|
|
73
|
+
"parse-failed",
|
|
74
|
+
"schema-invalid",
|
|
75
|
+
"incomplete",
|
|
76
|
+
"ok",
|
|
77
|
+
]);
|
|
78
|
+
|
|
53
79
|
function finding(checkClass, kind, code, message, extra) {
|
|
54
80
|
return { class: checkClass, kind, code, message, ...(extra ?? {}) };
|
|
55
81
|
}
|
|
56
82
|
|
|
57
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Loads one JSON document and classifies it into the stable document states.
|
|
85
|
+
* Options:
|
|
86
|
+
* schemaObject — when given, an ok state additionally requires the parsed
|
|
87
|
+
* document to pass the registered schema of that object;
|
|
88
|
+
* completeWhen — when given, an ok state additionally requires the predicate
|
|
89
|
+
* to hold on the parsed value (absence => "incomplete").
|
|
90
|
+
*/
|
|
91
|
+
async function loadDocument(rootAbs, relPath, { schemaObject, completeWhen } = {}) {
|
|
92
|
+
const loaded = await readOptionalJson(rootAbs, relPath);
|
|
93
|
+
if (loaded.reason === "missing") return { state: "missing", value: null };
|
|
94
|
+
if (!loaded.ok) return { state: "parse-failed", value: null };
|
|
95
|
+
if (schemaObject !== undefined) {
|
|
96
|
+
const registration = findSchemaByObject(schemaObject);
|
|
97
|
+
const outcome = validateContractDocument(loaded.value, { schemaId: registration.$id });
|
|
98
|
+
if (!outcome.valid) return { state: "schema-invalid", value: loaded.value, outcome };
|
|
99
|
+
}
|
|
100
|
+
if (completeWhen !== undefined && !completeWhen(loaded.value)) {
|
|
101
|
+
return { state: "incomplete", value: loaded.value };
|
|
102
|
+
}
|
|
103
|
+
return { state: "ok", value: loaded.value };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function checkContracts(rootAbs, findings) {
|
|
58
107
|
const targets = [
|
|
59
108
|
["project-manifest", PROJECT_MANIFEST_PATH],
|
|
60
109
|
["managed-file-lock", MANAGED_LOCK_PATH],
|
|
61
110
|
];
|
|
62
111
|
let present = 0;
|
|
63
112
|
for (const [objectName, relPath] of targets) {
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
113
|
+
const document = await loadDocument(rootAbs, relPath, { schemaObject: objectName });
|
|
114
|
+
if (document.state === "missing") continue;
|
|
66
115
|
present += 1;
|
|
67
|
-
|
|
68
|
-
if (!loaded.ok) {
|
|
116
|
+
if (document.state === "parse-failed") {
|
|
69
117
|
findings.push(
|
|
70
118
|
finding(
|
|
71
119
|
"contracts",
|
|
72
120
|
KIT_ERROR_KINDS.CONTRACT_PARSE_FAILED,
|
|
73
121
|
"SFC1001",
|
|
74
122
|
`${relPath} is not valid JSON`,
|
|
75
|
-
{ path: relPath },
|
|
123
|
+
{ path: relPath, documentState: document.state },
|
|
76
124
|
),
|
|
77
125
|
);
|
|
78
126
|
continue;
|
|
79
127
|
}
|
|
80
|
-
|
|
81
|
-
const outcome = validateContractDocument(loaded.value, { schemaId: registration.$id });
|
|
82
|
-
if (!outcome.valid) {
|
|
128
|
+
if (document.state === "schema-invalid") {
|
|
83
129
|
findings.push(
|
|
84
130
|
finding(
|
|
85
131
|
"contracts",
|
|
86
132
|
"schema-validation-failed",
|
|
87
|
-
|
|
88
|
-
`${relPath} fails the registered ${objectName} schema: ${outcome.errors
|
|
133
|
+
"SFC1001",
|
|
134
|
+
`${relPath} fails the registered ${objectName} schema: ${document.outcome.errors
|
|
89
135
|
.slice(0, 3)
|
|
90
136
|
.map((entry) => `${entry.instancePath || "/"} ${entry.message}`)
|
|
91
137
|
.join("; ")}`,
|
|
92
|
-
{ path: relPath, schemaId:
|
|
138
|
+
{ path: relPath, schemaId: findSchemaByObject(objectName).$id, documentState: document.state },
|
|
93
139
|
),
|
|
94
140
|
);
|
|
141
|
+
continue;
|
|
95
142
|
}
|
|
96
143
|
}
|
|
97
144
|
if (present === 0) {
|
|
@@ -188,7 +235,10 @@ async function checkDrift(rootAbs, findings) {
|
|
|
188
235
|
return lock;
|
|
189
236
|
}
|
|
190
237
|
|
|
191
|
-
async function checkClosure(rootAbs,
|
|
238
|
+
async function checkClosure(rootAbs, findings) {
|
|
239
|
+
// The closure class loads its own lock: it never depends on the drift
|
|
240
|
+
// class having run first.
|
|
241
|
+
const lock = await loadManagedFileLock(rootAbs);
|
|
192
242
|
if (!lock) {
|
|
193
243
|
findings.push(
|
|
194
244
|
finding(
|
|
@@ -241,26 +291,49 @@ async function checkClosure(rootAbs, lock, findings) {
|
|
|
241
291
|
}
|
|
242
292
|
}
|
|
243
293
|
|
|
244
|
-
async function checkVersion(rootAbs,
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const declared = manifest?.contracts?.version;
|
|
252
|
-
if (typeof declared !== "string") {
|
|
294
|
+
async function checkVersion(rootAbs, findings) {
|
|
295
|
+
// Loads its own manifest without schema validation: schema conformance is
|
|
296
|
+
// the contracts class's finding, the version class only needs the fact.
|
|
297
|
+
const document = await loadDocument(rootAbs, PROJECT_MANIFEST_PATH, {
|
|
298
|
+
completeWhen: (value) => typeof value?.contracts?.version === "string",
|
|
299
|
+
});
|
|
300
|
+
if (document.state === "missing") {
|
|
253
301
|
findings.push(
|
|
254
302
|
finding(
|
|
255
303
|
"version",
|
|
256
304
|
KIT_ERROR_KINDS.CONTRACTS_MISSING,
|
|
257
305
|
"SFC2004",
|
|
258
|
-
"project manifest is absent
|
|
259
|
-
{ path: PROJECT_MANIFEST_PATH },
|
|
306
|
+
"project manifest is absent; version check cannot run without it",
|
|
307
|
+
{ path: PROJECT_MANIFEST_PATH, documentState: document.state },
|
|
308
|
+
),
|
|
309
|
+
);
|
|
310
|
+
return { contractsVersion: null };
|
|
311
|
+
}
|
|
312
|
+
if (document.state === "parse-failed") {
|
|
313
|
+
findings.push(
|
|
314
|
+
finding(
|
|
315
|
+
"version",
|
|
316
|
+
KIT_ERROR_KINDS.CONTRACT_PARSE_FAILED,
|
|
317
|
+
"SFC2004",
|
|
318
|
+
"project manifest is not valid JSON; version check cannot run without it",
|
|
319
|
+
{ path: PROJECT_MANIFEST_PATH, documentState: document.state },
|
|
320
|
+
),
|
|
321
|
+
);
|
|
322
|
+
return { contractsVersion: null };
|
|
323
|
+
}
|
|
324
|
+
if (document.state === "incomplete") {
|
|
325
|
+
findings.push(
|
|
326
|
+
finding(
|
|
327
|
+
"version",
|
|
328
|
+
KIT_ERROR_KINDS.DOCUMENT_INCOMPLETE,
|
|
329
|
+
"SFC2004",
|
|
330
|
+
"project manifest lacks contracts.version; version check cannot run without it",
|
|
331
|
+
{ path: PROJECT_MANIFEST_PATH, documentState: document.state },
|
|
260
332
|
),
|
|
261
333
|
);
|
|
262
334
|
return { contractsVersion: null };
|
|
263
335
|
}
|
|
336
|
+
const declared = document.value.contracts.version;
|
|
264
337
|
if (declared !== CONTRACTS_VERSION) {
|
|
265
338
|
findings.push(
|
|
266
339
|
finding(
|
|
@@ -275,7 +348,9 @@ async function checkVersion(rootAbs, docs, findings) {
|
|
|
275
348
|
return { contractsVersion: declared };
|
|
276
349
|
}
|
|
277
350
|
|
|
278
|
-
async function checkDocs(rootAbs,
|
|
351
|
+
async function checkDocs(rootAbs, findings) {
|
|
352
|
+
// The docs class reads every documentation fact itself: README, package.json
|
|
353
|
+
// and the project manifest. It never consumes another class's state.
|
|
279
354
|
const readme = await readOptionalFile(rootAbs, "README.md");
|
|
280
355
|
if (readme === null || readme.trim().length === 0) {
|
|
281
356
|
findings.push(
|
|
@@ -284,26 +359,57 @@ async function checkDocs(rootAbs, docs, findings) {
|
|
|
284
359
|
KIT_ERROR_KINDS.README_MISSING,
|
|
285
360
|
"SFC2004",
|
|
286
361
|
"README.md is missing or empty (documentation fact)",
|
|
287
|
-
{ path: "README.md" },
|
|
362
|
+
{ path: "README.md", documentState: "missing" },
|
|
288
363
|
),
|
|
289
364
|
);
|
|
290
365
|
}
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
366
|
+
const packageDocument = await loadDocument(rootAbs, "package.json");
|
|
367
|
+
if (packageDocument.state === "parse-failed") {
|
|
368
|
+
findings.push(
|
|
369
|
+
finding(
|
|
370
|
+
"docs",
|
|
371
|
+
KIT_ERROR_KINDS.CONTRACT_PARSE_FAILED,
|
|
372
|
+
"SFC2004",
|
|
373
|
+
"package.json is not valid JSON; documentation identity facts cannot be compared",
|
|
374
|
+
{ path: "package.json", documentState: packageDocument.state },
|
|
375
|
+
),
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
const manifestDocument = await loadDocument(rootAbs, PROJECT_MANIFEST_PATH, {
|
|
379
|
+
completeWhen: (value) => typeof value?.project?.id === "string",
|
|
380
|
+
});
|
|
381
|
+
if (manifestDocument.state === "parse-failed") {
|
|
382
|
+
findings.push(
|
|
383
|
+
finding(
|
|
384
|
+
"docs",
|
|
385
|
+
KIT_ERROR_KINDS.CONTRACT_PARSE_FAILED,
|
|
386
|
+
"SFC2004",
|
|
387
|
+
"project manifest is not valid JSON; documentation identity facts cannot be compared",
|
|
388
|
+
{ path: PROJECT_MANIFEST_PATH, documentState: manifestDocument.state },
|
|
389
|
+
),
|
|
390
|
+
);
|
|
391
|
+
} else if (manifestDocument.state === "incomplete") {
|
|
392
|
+
findings.push(
|
|
393
|
+
finding(
|
|
394
|
+
"docs",
|
|
395
|
+
KIT_ERROR_KINDS.DOCUMENT_INCOMPLETE,
|
|
396
|
+
"SFC2004",
|
|
397
|
+
"project manifest lacks a comparable project.id; documentation identity facts cannot be compared",
|
|
398
|
+
{ path: PROJECT_MANIFEST_PATH, documentState: manifestDocument.state },
|
|
399
|
+
),
|
|
400
|
+
);
|
|
401
|
+
} else if (manifestDocument.state === "ok" && packageDocument.state === "ok") {
|
|
402
|
+
const packageName = packageDocument.value?.name;
|
|
403
|
+
if (typeof packageName === "string" && manifestDocument.value.project.id !== packageName) {
|
|
404
|
+
findings.push(
|
|
405
|
+
finding(
|
|
406
|
+
"docs",
|
|
407
|
+
KIT_ERROR_KINDS.IDENTITY_MISMATCH,
|
|
408
|
+
"SFC2004",
|
|
409
|
+
`project manifest id "${manifestDocument.value.project.id}" does not match package.json name "${packageName}"`,
|
|
410
|
+
{ manifestId: manifestDocument.value.project.id, packageName },
|
|
411
|
+
),
|
|
412
|
+
);
|
|
307
413
|
}
|
|
308
414
|
}
|
|
309
415
|
}
|
|
@@ -333,7 +439,7 @@ async function checkGit(rootAbs, findings, allowGitSpawn) {
|
|
|
333
439
|
return git;
|
|
334
440
|
}
|
|
335
441
|
|
|
336
|
-
async function checkIdentity(rootAbs,
|
|
442
|
+
async function checkIdentity(rootAbs, findings, profilesRoot) {
|
|
337
443
|
// Load identity record
|
|
338
444
|
const identityRecord = await loadIdentityRecord(rootAbs);
|
|
339
445
|
|
|
@@ -350,9 +456,6 @@ async function checkIdentity(rootAbs, docs, findings, profilesRoot) {
|
|
|
350
456
|
return null;
|
|
351
457
|
}
|
|
352
458
|
|
|
353
|
-
// Store for later use
|
|
354
|
-
docs["skill-family.identity-record.json"] = identityRecord;
|
|
355
|
-
|
|
356
459
|
// Validate against profile
|
|
357
460
|
if (profilesRoot) {
|
|
358
461
|
const profileValidation = await validateIdentityAgainstProfile(identityRecord, profilesRoot);
|
|
@@ -388,9 +491,14 @@ async function checkIdentity(rootAbs, docs, findings, profilesRoot) {
|
|
|
388
491
|
|
|
389
492
|
/**
|
|
390
493
|
* Runs all check classes over one target.
|
|
391
|
-
* Options: { root, allowGitSpawn, profilesRoot }.
|
|
494
|
+
* Options: { root, allowGitSpawn, only, profilesRoot }.
|
|
392
495
|
* Returns the report document. Never writes anywhere; throws KitError only
|
|
393
496
|
* for unusable inputs (an unreadable target).
|
|
497
|
+
*
|
|
498
|
+
* Class results distinguish three facts: selected (included by --only),
|
|
499
|
+
* completed (actually ran to its end), and findings (count). A class that
|
|
500
|
+
* throws mid-flight stays completed=false and contributes a mechanism
|
|
501
|
+
* finding; the report's mechanism flag maps to exit code 2.
|
|
394
502
|
*/
|
|
395
503
|
export async function runChecks({ root, allowGitSpawn = true, only, profilesRoot } = {}) {
|
|
396
504
|
if (only !== undefined && !CHECK_CLASSES.includes(only)) {
|
|
@@ -398,31 +506,60 @@ export async function runChecks({ root, allowGitSpawn = true, only, profilesRoot
|
|
|
398
506
|
}
|
|
399
507
|
const rootAbs = await resolveTargetRoot(root ?? ".");
|
|
400
508
|
const findings = [];
|
|
401
|
-
const
|
|
509
|
+
const selected = new Set(only === undefined ? CHECK_CLASSES : [only]);
|
|
510
|
+
const completed = new Set();
|
|
511
|
+
let mechanismFailure = false;
|
|
402
512
|
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
513
|
+
const runners = {
|
|
514
|
+
contracts: () => checkContracts(rootAbs, findings),
|
|
515
|
+
drift: () => checkDrift(rootAbs, findings),
|
|
516
|
+
closure: () => checkClosure(rootAbs, findings),
|
|
517
|
+
version: () => checkVersion(rootAbs, findings),
|
|
518
|
+
docs: () => checkDocs(rootAbs, findings),
|
|
519
|
+
git: () => checkGit(rootAbs, findings, allowGitSpawn),
|
|
520
|
+
identity: () => checkIdentity(rootAbs, findings, profilesRoot),
|
|
521
|
+
};
|
|
410
522
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
523
|
+
const classData = {
|
|
524
|
+
closure: { digest: null, note: "not-selected" },
|
|
525
|
+
git: null,
|
|
526
|
+
version: { contractsVersion: null },
|
|
527
|
+
identity: null,
|
|
528
|
+
};
|
|
415
529
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
530
|
+
for (const name of CHECK_CLASSES) {
|
|
531
|
+
if (!selected.has(name)) continue;
|
|
532
|
+
try {
|
|
533
|
+
const result = await runners[name]();
|
|
534
|
+
completed.add(name);
|
|
535
|
+
if (name === "closure") classData.closure = result;
|
|
536
|
+
if (name === "git") classData.git = result;
|
|
537
|
+
if (name === "version") classData.version = result;
|
|
538
|
+
if (name === "identity") {
|
|
539
|
+
classData.identity = result
|
|
540
|
+
? { record: result, licensing: result.licensing, authors: result.authors }
|
|
541
|
+
: null;
|
|
542
|
+
}
|
|
543
|
+
} catch (cause) {
|
|
544
|
+
// A selected class that could not finish is a mechanism finding, never a
|
|
545
|
+
// silent skip: completed stays false and the report says so.
|
|
546
|
+
mechanismFailure = true;
|
|
547
|
+
const causeKind =
|
|
548
|
+
cause && cause.details && typeof cause.details.kind === "string"
|
|
549
|
+
? cause.details.kind
|
|
550
|
+
: "unknown";
|
|
551
|
+
findings.push(
|
|
552
|
+
finding(
|
|
553
|
+
name,
|
|
554
|
+
KIT_ERROR_KINDS.CHECK_CLASS_FAILED,
|
|
555
|
+
"SFC2004",
|
|
556
|
+
`check class '${name}' could not complete: ${cause && cause.message ? cause.message : String(cause)}`,
|
|
557
|
+
{ causeKind },
|
|
558
|
+
),
|
|
559
|
+
);
|
|
560
|
+
}
|
|
419
561
|
}
|
|
420
562
|
|
|
421
|
-
if (classes.includes("version")) versionInfo = await checkVersion(rootAbs, docs, findings);
|
|
422
|
-
if (classes.includes("docs")) await checkDocs(rootAbs, docs, findings);
|
|
423
|
-
if (classes.includes("git")) git = await checkGit(rootAbs, findings, allowGitSpawn);
|
|
424
|
-
if (classes.includes("identity")) identityRecord = await checkIdentity(rootAbs, docs, findings, profilesRoot);
|
|
425
|
-
|
|
426
563
|
const byClass = {};
|
|
427
564
|
for (const item of findings) {
|
|
428
565
|
byClass[item.class] = (byClass[item.class] ?? 0) + 1;
|
|
@@ -446,22 +583,20 @@ export async function runChecks({ root, allowGitSpawn = true, only, profilesRoot
|
|
|
446
583
|
generatedBy: { tool: KIT_TOOL_NAME, version: KIT_VERSION },
|
|
447
584
|
target: { root: ".", entryCount: entries.length },
|
|
448
585
|
ok: findings.length === 0,
|
|
586
|
+
mechanism: mechanismFailure,
|
|
449
587
|
classes: CHECK_CLASSES.map((name) => ({
|
|
450
588
|
name,
|
|
451
|
-
|
|
589
|
+
selected: selected.has(name),
|
|
590
|
+
completed: completed.has(name),
|
|
452
591
|
findings: byClass[name] ?? 0,
|
|
453
592
|
})),
|
|
454
593
|
findings,
|
|
455
594
|
data: {
|
|
456
|
-
closure:
|
|
457
|
-
git,
|
|
458
|
-
version:
|
|
595
|
+
closure: classData.closure,
|
|
596
|
+
git: classData.git,
|
|
597
|
+
version: classData.version,
|
|
459
598
|
managedDeclarations,
|
|
460
|
-
identity:
|
|
461
|
-
record: identityRecord,
|
|
462
|
-
licensing: identityRecord.licensing,
|
|
463
|
-
authors: identityRecord.authors,
|
|
464
|
-
} : null,
|
|
599
|
+
identity: classData.identity,
|
|
465
600
|
},
|
|
466
601
|
policy:
|
|
467
602
|
"check is diagnosis only: it never writes, never fixes, and never calls git write commands; findings must be resolved by a human or an authorized generator",
|