speckeeper 0.9.1 → 0.9.3
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/README.md +39 -0
- package/cli-contract.yaml +574 -0
- package/dist/cli.js +84 -9
- package/dist/cli.js.map +1 -1
- package/dist/{config-api-CfxXt9Zt.d.ts → config-api-CLVjdgIP.d.ts} +5 -0
- package/dist/dsl/index.d.ts +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js.map +1 -1
- package/docs/cli-reference.md +449 -0
- package/docs/design/actors.md +72 -0
- package/docs/design/arch-actors.md +42 -0
- package/docs/design/artifacts.md +69 -0
- package/docs/design/cli-commands.md +332 -0
- package/docs/design/constraints.md +66 -0
- package/docs/design/containers.md +86 -0
- package/docs/design/entities.md +161 -0
- package/docs/design/external-systems.md +42 -0
- package/docs/design/functional-requirements.md +810 -0
- package/docs/design/glossary.md +242 -0
- package/docs/design/nonfunctional-requirements.md +242 -0
- package/docs/design/test-refs.md +258 -0
- package/docs/design/usecases.md +114 -0
- package/docs/directory-entries.md +22 -0
- package/docs/framework_requirements_spec.md +1052 -0
- package/docs/model-guide.md +762 -0
- package/docs/model_entity_catalog.md +67 -0
- package/docs/scaffold-mermaid-spec.md +357 -0
- package/package.json +7 -2
package/dist/cli.js
CHANGED
|
@@ -939,6 +939,8 @@ async function checkCommand(type, options) {
|
|
|
939
939
|
console.log(chalk4.gray(` Sources: ${sources.length} configured`));
|
|
940
940
|
}
|
|
941
941
|
const results = [];
|
|
942
|
+
const transitiveRelations = config.coverage?.transitiveRelations ?? [];
|
|
943
|
+
let transitiveCoverageData;
|
|
942
944
|
const allSpecIds = [];
|
|
943
945
|
const specIdToModel = /* @__PURE__ */ new Map();
|
|
944
946
|
const lookupKeyMap = /* @__PURE__ */ new Map();
|
|
@@ -1010,9 +1012,20 @@ async function checkCommand(type, options) {
|
|
|
1010
1012
|
}
|
|
1011
1013
|
}
|
|
1012
1014
|
}
|
|
1015
|
+
const directlyCovered = new Set(matches.keys());
|
|
1016
|
+
let coveredSet = directlyCovered;
|
|
1017
|
+
if (transitiveRelations.length > 0) {
|
|
1018
|
+
const allSpecsWithRelations = allSpecIds.map((id) => {
|
|
1019
|
+
const entry = specIdToModel.get(id);
|
|
1020
|
+
const spec = entry?.spec;
|
|
1021
|
+
return { id, relations: spec?.relations };
|
|
1022
|
+
});
|
|
1023
|
+
transitiveCoverageData = computeTransitiveCoverage(directlyCovered, allSpecsWithRelations, transitiveRelations);
|
|
1024
|
+
coveredSet = transitiveCoverageData.coveredSet;
|
|
1025
|
+
}
|
|
1013
1026
|
if (options.verbose) {
|
|
1014
1027
|
for (const specId of allSpecIds) {
|
|
1015
|
-
if (!
|
|
1028
|
+
if (!coveredSet.has(specId)) {
|
|
1016
1029
|
const entry = specIdToModel.get(specId);
|
|
1017
1030
|
results.push({
|
|
1018
1031
|
type: entry?.modelId ?? "unknown",
|
|
@@ -1048,17 +1061,47 @@ async function checkCommand(type, options) {
|
|
|
1048
1061
|
});
|
|
1049
1062
|
}
|
|
1050
1063
|
}
|
|
1064
|
+
}
|
|
1065
|
+
if (transitiveCoverageData && transitiveRelations.length > 0) {
|
|
1066
|
+
const total = allSpecIds.length;
|
|
1067
|
+
const directCount = transitiveCoverageData.directCount;
|
|
1068
|
+
const transitiveCount = transitiveCoverageData.transitiveCount;
|
|
1069
|
+
const covered = directCount + transitiveCount;
|
|
1070
|
+
const uncovered = total - covered;
|
|
1071
|
+
const coveragePercent = total > 0 ? Math.round(covered / total * 100) : 100;
|
|
1051
1072
|
console.log("");
|
|
1052
|
-
console.log(chalk4.
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1073
|
+
console.log(chalk4.blue(` Transitive coverage (via ${transitiveRelations.join(", ")})`));
|
|
1074
|
+
console.log(chalk4.gray(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`));
|
|
1075
|
+
console.log(chalk4.gray(` Total: ${total}`));
|
|
1076
|
+
console.log(chalk4.green(` Covered: ${covered} (${directCount} direct + ${transitiveCount} transitive)`));
|
|
1077
|
+
console.log(chalk4.yellow(` Uncovered: ${uncovered}`));
|
|
1078
|
+
const color = coveragePercent >= 80 ? chalk4.green : coveragePercent >= 50 ? chalk4.yellow : chalk4.red;
|
|
1079
|
+
console.log(color(` Coverage: ${coveragePercent}%`));
|
|
1080
|
+
if (uncovered > 0) {
|
|
1081
|
+
const uncoveredIds = allSpecIds.filter((id) => !transitiveCoverageData.coveredSet.has(id));
|
|
1082
|
+
const display = uncoveredIds.slice(0, 10);
|
|
1083
|
+
console.log("");
|
|
1084
|
+
console.log(chalk4.yellow(" Uncovered items:"));
|
|
1085
|
+
for (const id of display) {
|
|
1086
|
+
const entry = specIdToModel.get(id);
|
|
1087
|
+
console.log(chalk4.yellow(` - ${id} (${entry?.modelId ?? "unknown"})`));
|
|
1088
|
+
}
|
|
1089
|
+
if (uncoveredIds.length > 10) {
|
|
1090
|
+
console.log(chalk4.yellow(` ... and ${uncoveredIds.length - 10} more`));
|
|
1091
|
+
}
|
|
1059
1092
|
}
|
|
1060
|
-
}
|
|
1093
|
+
}
|
|
1094
|
+
console.log("");
|
|
1095
|
+
console.log(chalk4.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1096
|
+
const allCoverageResults = [...coverageResults];
|
|
1097
|
+
const allPassed = allCoverageResults.every((c) => c.result.coveragePercent >= 80);
|
|
1098
|
+
if (coverageResults.length === 0 && !transitiveCoverageData) {
|
|
1061
1099
|
console.log(chalk4.gray(" No coverage checker found"));
|
|
1100
|
+
} else if (allPassed) {
|
|
1101
|
+
console.log(chalk4.green(" \u2713 All coverage checks passed (\u226580%)"));
|
|
1102
|
+
} else {
|
|
1103
|
+
const failed = allCoverageResults.filter((c) => c.result.coveragePercent < 80);
|
|
1104
|
+
console.log(chalk4.yellow(` \u26A0 ${failed.length} coverage check(s) below 80%`));
|
|
1062
1105
|
}
|
|
1063
1106
|
}
|
|
1064
1107
|
const hasErrors = results.some((r) => !r.success);
|
|
@@ -1070,6 +1113,38 @@ async function checkCommand(type, options) {
|
|
|
1070
1113
|
process.exit(1);
|
|
1071
1114
|
}
|
|
1072
1115
|
}
|
|
1116
|
+
function computeTransitiveCoverage(directlyCovered, allSpecs, transitiveRelations) {
|
|
1117
|
+
if (transitiveRelations.length === 0) {
|
|
1118
|
+
return { coveredSet: new Set(directlyCovered), directCount: directlyCovered.size, transitiveCount: 0 };
|
|
1119
|
+
}
|
|
1120
|
+
const reverseRelations = /* @__PURE__ */ new Map();
|
|
1121
|
+
for (const spec of allSpecs) {
|
|
1122
|
+
if (!spec.relations) continue;
|
|
1123
|
+
for (const rel of spec.relations) {
|
|
1124
|
+
if (!transitiveRelations.includes(rel.type)) continue;
|
|
1125
|
+
const existing = reverseRelations.get(rel.target) ?? [];
|
|
1126
|
+
existing.push(spec.id);
|
|
1127
|
+
reverseRelations.set(rel.target, existing);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
const coveredSet = new Set(directlyCovered);
|
|
1131
|
+
let changed = true;
|
|
1132
|
+
while (changed) {
|
|
1133
|
+
changed = false;
|
|
1134
|
+
for (const [targetId, sourceIds] of reverseRelations) {
|
|
1135
|
+
if (coveredSet.has(targetId)) continue;
|
|
1136
|
+
if (sourceIds.length > 0 && sourceIds.every((id) => coveredSet.has(id))) {
|
|
1137
|
+
coveredSet.add(targetId);
|
|
1138
|
+
changed = true;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
return {
|
|
1143
|
+
coveredSet,
|
|
1144
|
+
directCount: directlyCovered.size,
|
|
1145
|
+
transitiveCount: coveredSet.size - directlyCovered.size
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1073
1148
|
function loadExternalData(filePath) {
|
|
1074
1149
|
const content = readFileSync(filePath, "utf-8");
|
|
1075
1150
|
if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
|