qatools-cli 1.1.1 → 1.1.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/package.json +1 -1
- package/qatools.js +227 -5
package/package.json
CHANGED
package/qatools.js
CHANGED
|
@@ -923,10 +923,11 @@ program
|
|
|
923
923
|
|
|
924
924
|
program
|
|
925
925
|
.command("requirement-update")
|
|
926
|
-
.description("Update one or more requirements (by id, optionally from a JSON file)")
|
|
927
|
-
.option("-f, --file <path>", "JSON file with updates: { '<requirementId>': { status, epicKey, ... } }")
|
|
926
|
+
.description("Update one or more requirements (by id, optionally from a JSON file). status=implemented/tested REQUIRES --evidence.")
|
|
927
|
+
.option("-f, --file <path>", "JSON file with updates: { '<requirementId>': { status, epicKey, evidence, ... } }")
|
|
928
928
|
.option("-i, --id <id>", "Single requirement ID to update")
|
|
929
929
|
.option("--status <status>", "New status (draft/approved/rejected/implemented/tested/superseded)")
|
|
930
|
+
.option("--evidence <text>", "REQUIRED when status=implemented or tested. Cite test output, file:line, expected vs actual, or 'no test applies because <reason>'.")
|
|
930
931
|
.option("--epic <key>", "Set epicKey")
|
|
931
932
|
.option("--sprint <id>", "Set sprintId (use empty string to clear)")
|
|
932
933
|
.option("--type <type>", "Set type (functional/non-functional)")
|
|
@@ -943,8 +944,16 @@ program
|
|
|
943
944
|
if (!projectId) { console.error("No project configured. Run: qatools setup, or pass --project <id>"); process.exit(1); }
|
|
944
945
|
|
|
945
946
|
let updates = {};
|
|
947
|
+
let evidenceById = {}; // requirementId -> evidence text
|
|
946
948
|
if (options.file) {
|
|
947
949
|
updates = JSON.parse(fs.readFileSync(path.resolve(options.file), "utf8"));
|
|
950
|
+
// In file mode, evidence is per-update inline (e.g. { status: 'tested', evidence: '...' })
|
|
951
|
+
for (const [id, u] of Object.entries(updates)) {
|
|
952
|
+
if (u.evidence) {
|
|
953
|
+
evidenceById[id] = u.evidence;
|
|
954
|
+
delete u.evidence; // separate from updates so backend handles via dedicated arg
|
|
955
|
+
}
|
|
956
|
+
}
|
|
948
957
|
} else if (options.id) {
|
|
949
958
|
const u = {};
|
|
950
959
|
if (options.status) u.status = options.status;
|
|
@@ -955,16 +964,48 @@ program
|
|
|
955
964
|
if (options.description) u.description = options.description;
|
|
956
965
|
if (options.ac) u.acceptanceCriteria = options.ac;
|
|
957
966
|
updates[options.id] = u;
|
|
967
|
+
if (options.evidence) evidenceById[options.id] = options.evidence;
|
|
958
968
|
} else {
|
|
959
969
|
console.error("Provide --file or --id");
|
|
960
970
|
process.exit(1);
|
|
961
971
|
}
|
|
962
972
|
|
|
973
|
+
// Pre-flight: refuse status=implemented/tested without evidence (catch before round-trip)
|
|
974
|
+
const REQUIRES_EVIDENCE = new Set(['implemented', 'tested']);
|
|
975
|
+
const violations = [];
|
|
976
|
+
for (const [id, u] of Object.entries(updates)) {
|
|
977
|
+
if (u.status && REQUIRES_EVIDENCE.has(u.status)) {
|
|
978
|
+
const ev = (evidenceById[id] || '').trim();
|
|
979
|
+
if (ev.length < 10) violations.push({ id, status: u.status });
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (violations.length > 0) {
|
|
983
|
+
console.error("");
|
|
984
|
+
console.error("✗ Refusing to set status=implemented/tested without --evidence:");
|
|
985
|
+
console.error("");
|
|
986
|
+
for (const v of violations) {
|
|
987
|
+
console.error(` • ${v.id} → status=${v.status} (no evidence given)`);
|
|
988
|
+
}
|
|
989
|
+
console.error("");
|
|
990
|
+
console.error(" Evidence-driven status: cite a concrete verification before claiming done.");
|
|
991
|
+
console.error(" Examples:");
|
|
992
|
+
console.error(" --evidence \"Test AUTH-1.3 passed in session abc123, response was 302 to /login\"");
|
|
993
|
+
console.error(" --evidence \"Manual: input mass=10 kg, rate=5 kr/kg → got 50 kr (matches expected)\"");
|
|
994
|
+
console.error(" --evidence \"qa.service.ts:340 — checkProjectAccess now returns 403 for cross-project keys\"");
|
|
995
|
+
console.error(" --evidence \"No test applies — pure docs change in README.md, no executable behavior\"");
|
|
996
|
+
console.error("");
|
|
997
|
+
console.error(" Min 10 chars. If you cannot articulate the verification, use status=ready_for_test instead.");
|
|
998
|
+
console.error("");
|
|
999
|
+
process.exit(2);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
963
1002
|
const updateReq = httpsCallable(functions, "updateRequirement");
|
|
964
1003
|
let ok = 0, failed = 0;
|
|
965
1004
|
for (const [requirementId, fields] of Object.entries(updates)) {
|
|
966
1005
|
try {
|
|
967
|
-
|
|
1006
|
+
const payload = { projectId, requirementId, updates: fields };
|
|
1007
|
+
if (evidenceById[requirementId]) payload.evidence = evidenceById[requirementId];
|
|
1008
|
+
await updateReq(payload);
|
|
968
1009
|
ok++;
|
|
969
1010
|
process.stdout.write(`\r Updated ${ok}, failed ${failed} / ${Object.keys(updates).length}`);
|
|
970
1011
|
} catch (e) {
|
|
@@ -1093,10 +1134,11 @@ program
|
|
|
1093
1134
|
|
|
1094
1135
|
program
|
|
1095
1136
|
.command("backlog-update")
|
|
1096
|
-
.description("Update one or more backlog items (
|
|
1097
|
-
.option("-f, --file <path>", "JSON file with updates: { '<id>': { status, comment, ... } }")
|
|
1137
|
+
.description("Update one or more backlog items. status=resolved REQUIRES --evidence (cite the verification).")
|
|
1138
|
+
.option("-f, --file <path>", "JSON file with updates: { '<id>': { status, evidence, comment, ... } }")
|
|
1098
1139
|
.option("-i, --id <id>", "Single item ID to update")
|
|
1099
1140
|
.option("--status <status>", "New status")
|
|
1141
|
+
.option("--evidence <text>", "REQUIRED when status=resolved (except for GAP). Cite the verification: test output, file:line, expected vs actual, or 'no test applies because <reason>'.")
|
|
1100
1142
|
.option("--comment <text>", "Add comment")
|
|
1101
1143
|
.option("--assigned <email>", "Assign to")
|
|
1102
1144
|
.option("--sprint <id>", "Set sprint ID")
|
|
@@ -1106,6 +1148,8 @@ program
|
|
|
1106
1148
|
.option("--tags <tags>", "Set tags (comma-separated, replaces existing)")
|
|
1107
1149
|
.option("--description <text>", "Update description")
|
|
1108
1150
|
.option("--author <name>", "Author name for comments (default: your email)", "Claude")
|
|
1151
|
+
.option("--skip-test-verification", "Allow setting status=resolved without going through ready_for_test (use only when no test applies). Evidence is still required.")
|
|
1152
|
+
.option("--project <id>", "Target project ID (overrides default; needed for status pre-check)")
|
|
1109
1153
|
.option("-e, --email <email>", "Login email")
|
|
1110
1154
|
.option("-p, --password <password>", "Login password")
|
|
1111
1155
|
.action(async (options) => {
|
|
@@ -1121,6 +1165,7 @@ program
|
|
|
1121
1165
|
} else if (options.id) {
|
|
1122
1166
|
const u = {};
|
|
1123
1167
|
if (options.status) u.status = options.status;
|
|
1168
|
+
if (options.evidence) u.evidence = options.evidence;
|
|
1124
1169
|
if (options.comment) { u.comment = options.comment; u.authorName = options.author; }
|
|
1125
1170
|
if (options.assigned) u.assignedTo = options.assigned;
|
|
1126
1171
|
if (options.developer) u.developer = options.developer;
|
|
@@ -1135,6 +1180,105 @@ program
|
|
|
1135
1180
|
process.exit(1);
|
|
1136
1181
|
}
|
|
1137
1182
|
|
|
1183
|
+
// Pre-flight: refuse status=resolved without evidence (catch before round-trip)
|
|
1184
|
+
// Need to fetch items to know their type (GAP is exempt)
|
|
1185
|
+
const settingResolved = Object.entries(updates).filter(([, u]) => u.status === 'resolved');
|
|
1186
|
+
if (settingResolved.length > 0) {
|
|
1187
|
+
const creds = getCredentials();
|
|
1188
|
+
const preCheckProjectId = options.project || creds?.projectId;
|
|
1189
|
+
if (preCheckProjectId) {
|
|
1190
|
+
try {
|
|
1191
|
+
const listBacklog = httpsCallable(functions, "listBacklog");
|
|
1192
|
+
const listResult = await listBacklog({ projectId: preCheckProjectId });
|
|
1193
|
+
const allItems = listResult.data.items || [];
|
|
1194
|
+
const itemById = {};
|
|
1195
|
+
for (const it of allItems) itemById[it.id] = it;
|
|
1196
|
+
const evidenceViolations = [];
|
|
1197
|
+
for (const [id, u] of settingResolved) {
|
|
1198
|
+
const item = itemById[id];
|
|
1199
|
+
if (!item) continue;
|
|
1200
|
+
if (item.type === 'requirement_gap') continue; // GAP exempt from evidence
|
|
1201
|
+
const ev = (u.evidence || '').trim();
|
|
1202
|
+
if (ev.length < 10) {
|
|
1203
|
+
evidenceViolations.push({ id, type: item.type, currentStatus: item.status, description: (item.description || '').substring(0, 80) });
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
if (evidenceViolations.length > 0) {
|
|
1207
|
+
console.error("");
|
|
1208
|
+
console.error("✗ Refusing to set status=resolved without --evidence:");
|
|
1209
|
+
console.error("");
|
|
1210
|
+
for (const v of evidenceViolations) {
|
|
1211
|
+
console.error(` • ${v.id} type=${v.type} current=${v.currentStatus}`);
|
|
1212
|
+
console.error(` "${v.description}"`);
|
|
1213
|
+
}
|
|
1214
|
+
console.error("");
|
|
1215
|
+
console.error(" Evidence-driven status: cite a concrete verification before claiming resolved.");
|
|
1216
|
+
console.error(" Examples:");
|
|
1217
|
+
console.error(" --evidence \"Test SPRINT-1.2 passed in session abc123, item appeared in sprint list\"");
|
|
1218
|
+
console.error(" --evidence \"Reproduced bug then re-ran: input=10, expected=50, actual=50 (file.ts:42)\"");
|
|
1219
|
+
console.error(" --evidence \"No test applies — pure docs change in CHANGELOG.md\"");
|
|
1220
|
+
console.error("");
|
|
1221
|
+
console.error(" Min 10 chars. If you cannot articulate the verification, use status=ready_for_test instead.");
|
|
1222
|
+
console.error("");
|
|
1223
|
+
process.exit(2);
|
|
1224
|
+
}
|
|
1225
|
+
} catch (e) {
|
|
1226
|
+
// Pre-check failed (network etc) — fall through; backend will enforce
|
|
1227
|
+
console.error(`Warning: pre-flight evidence check failed (${e.message}). Backend will enforce.`);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// Pre-flight: enforce backlog flow. AVV/DEF/IMP cannot jump straight to resolved.
|
|
1233
|
+
// GAP is exempt (its "fix" is writing a requirement, not testing code).
|
|
1234
|
+
if (!options.skipTestVerification) {
|
|
1235
|
+
const settingResolved = Object.entries(updates).filter(([, u]) => u.status === 'resolved');
|
|
1236
|
+
if (settingResolved.length > 0) {
|
|
1237
|
+
const creds = getCredentials();
|
|
1238
|
+
const projectId = options.project || creds?.projectId;
|
|
1239
|
+
if (projectId) {
|
|
1240
|
+
try {
|
|
1241
|
+
const listBacklog = httpsCallable(functions, "listBacklog");
|
|
1242
|
+
const listResult = await listBacklog({ projectId });
|
|
1243
|
+
const allItems = listResult.data.items || [];
|
|
1244
|
+
const itemById = {};
|
|
1245
|
+
for (const it of allItems) itemById[it.id] = it;
|
|
1246
|
+
const violations = [];
|
|
1247
|
+
for (const [id] of settingResolved) {
|
|
1248
|
+
const item = itemById[id];
|
|
1249
|
+
if (!item) continue;
|
|
1250
|
+
if (item.type === 'requirement_gap') continue; // GAP exempt
|
|
1251
|
+
if (item.status === 'ready_for_test') continue; // legitimate transition
|
|
1252
|
+
violations.push({ id, type: item.type, currentStatus: item.status, description: (item.description || '').substring(0, 80) });
|
|
1253
|
+
}
|
|
1254
|
+
if (violations.length > 0) {
|
|
1255
|
+
console.error("");
|
|
1256
|
+
console.error("✗ Refusing to set status=resolved on items that haven't been through ready_for_test:");
|
|
1257
|
+
console.error("");
|
|
1258
|
+
for (const v of violations) {
|
|
1259
|
+
console.error(` • ${v.id} type=${v.type} current=${v.currentStatus}`);
|
|
1260
|
+
console.error(` "${v.description}"`);
|
|
1261
|
+
}
|
|
1262
|
+
console.error("");
|
|
1263
|
+
console.error(" Backlog flow: open → in_progress → ready_for_test → resolved");
|
|
1264
|
+
console.error(" AVV/DEF/IMP must be verified by a test before resolved.");
|
|
1265
|
+
console.error("");
|
|
1266
|
+
console.error(" Fix:");
|
|
1267
|
+
console.error(" a) Set status=ready_for_test instead and let a tester verify it");
|
|
1268
|
+
console.error(" b) Run a test session that covers this item, then flip to resolved");
|
|
1269
|
+
console.error(" c) If no test applies (e.g. pure docs), pass --skip-test-verification");
|
|
1270
|
+
console.error("");
|
|
1271
|
+
process.exit(2);
|
|
1272
|
+
}
|
|
1273
|
+
} catch (e) {
|
|
1274
|
+
console.error(`Warning: pre-flight check failed (${e.message}). Continuing without verification — pass --skip-test-verification to suppress this warning.`);
|
|
1275
|
+
}
|
|
1276
|
+
} else {
|
|
1277
|
+
console.error("Warning: no projectId configured — cannot pre-check backlog flow. Pass --project <id> for verification.");
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1138
1282
|
const updateItems = httpsCallable(functions, "updateBacklogItems");
|
|
1139
1283
|
try {
|
|
1140
1284
|
const result = await updateItems({ updates });
|
|
@@ -1719,6 +1863,83 @@ Every claim, verdict, analysis, and recommendation MUST cite specific evidence:
|
|
|
1719
1863
|
|
|
1720
1864
|
## If you're unsure
|
|
1721
1865
|
Say "I'm unsure" — that's better than guessing. Suggest what evidence would resolve the uncertainty.
|
|
1866
|
+
|
|
1867
|
+
## Specialized skills
|
|
1868
|
+
|
|
1869
|
+
There is a specific skill for **algorithmic and numerical** bugs: \`qa-numerical-verification.md\`. Read it when you're fixing a formula or any code where the bug is "wrong number" rather than "wrong structure".
|
|
1870
|
+
|
|
1871
|
+
If you encounter a different recurring bug class that needs its own verification pattern (UI regressions, race conditions, data integrity, etc.), create a NEW specific skill (\`qa-<class>-verification.md\`) rather than expanding this file or building a monolithic "fix-checklist". The pattern is: principle here, specific category in its own skill, CLI enforces evidence regardless of category. Don't build a generic checklist for hypothetical categories — let real biting cases drive the need.
|
|
1872
|
+
`,
|
|
1873
|
+
"qa-numerical-verification.md": `---
|
|
1874
|
+
description: How to verify algorithmic and numerical bug fixes. Read this when fixing a formula, calculation, or any code where the bug is "wrong number" rather than "wrong structure".
|
|
1875
|
+
---
|
|
1876
|
+
|
|
1877
|
+
# Numerical & Algorithmic Verification
|
|
1878
|
+
|
|
1879
|
+
Most evidence-first examples deal with structural bugs (NullReferenceException, wrong redirect, race condition). Algorithmic bugs are different — the code runs without crashing, it just produces the wrong number.
|
|
1880
|
+
|
|
1881
|
+
This skill is for those bugs. The verification pattern is non-negotiable.
|
|
1882
|
+
|
|
1883
|
+
## Before you start — entry-point checklist
|
|
1884
|
+
|
|
1885
|
+
Stop here and answer these BEFORE writing any verification code:
|
|
1886
|
+
|
|
1887
|
+
- [ ] Which input or test case actually triggers the bug? (Not "the system" — name a specific input.)
|
|
1888
|
+
- [ ] Does that input have non-zero, non-default values for the field the bug touches? (If everything is 0 or NULL, multiplying by anything gives the same result — the input cannot discriminate buggy from fixed code.)
|
|
1889
|
+
- [ ] If your input is non-discriminating: change it, or pick a different input. Never proceed with "the data doesn't show it".
|
|
1890
|
+
- [ ] Where will you write the expected and actual values? Answer: in the --evidence flag when you set status. If the values are large, also save them to a file you reference in evidence ("see /tmp/verify-2026-04-08.txt").
|
|
1891
|
+
- [ ] Can you compute the expected value by hand from the formula in under 60 seconds? If not, the formula is too complex for this verification — break it into pieces and verify each.
|
|
1892
|
+
|
|
1893
|
+
If any answer is "I don't know", stop and ask. Don't guess.
|
|
1894
|
+
|
|
1895
|
+
## The pattern
|
|
1896
|
+
|
|
1897
|
+
1. **Trivialize the input.** Pick values that make the formula readable. Set multipliers to 1, set inputs to round numbers (10, 100), set toggles to known states.
|
|
1898
|
+
2. **Compute the expected value by hand.** Write down the formula, plug in the trivialized inputs, get the expected number. Show your work.
|
|
1899
|
+
3. **Run the same input through the system.** Use whatever entry point the bug lives in — CLI, API, UI, test harness. Capture the actual output.
|
|
1900
|
+
4. **Compare with tolerance.** For floats, exact equality is fragile. Use a tolerance (e.g. abs(actual - expected) < 0.001) and document it.
|
|
1901
|
+
5. **Cite both numbers in evidence.** When you set status=tested, the --evidence flag must contain both the expected and actual value, plus the input that produced them.
|
|
1902
|
+
|
|
1903
|
+
## What "evidence" looks like for numerical bugs
|
|
1904
|
+
|
|
1905
|
+
❌ "Fixed the formula in calculation.ts"
|
|
1906
|
+
❌ "User confirmed the value looks right now"
|
|
1907
|
+
❌ "Tests pass" (which test? what value?)
|
|
1908
|
+
|
|
1909
|
+
✅ "Input: mass=10 kg, rate=5 kr/kg, alpha=0. Expected = 10*5 = 50. Actual = 50.0 (calc.ts:142). Diff < 0.001."
|
|
1910
|
+
✅ "Input: temp=20°C, time=3600s. Expected = 20*3600/1000 = 72 kWh. Actual = 72.0 (sim.py:88)."
|
|
1911
|
+
✅ "Trivialized: set kr_per_kg=1 to inspect the structure. Expected = mass*1 = mass = 10. Actual = 10.0. Now bumped kr_per_kg=5: expected=50, actual=50. Original bug was (1+alpha)*prod*rate*T which gave 132 for the same input."
|
|
1912
|
+
|
|
1913
|
+
## Why this matters
|
|
1914
|
+
|
|
1915
|
+
The difference between "I fixed it" and "I verified it" is the difference between a fix that holds and a fix that creates the next bug. For numerical code, "looks right" is not verification — the formula can be wrong by a constant factor and look fine until production.
|
|
1916
|
+
|
|
1917
|
+
## Specific traps
|
|
1918
|
+
|
|
1919
|
+
### 1. The dataset doesn't have a discriminating input
|
|
1920
|
+
If your test data has kr_per_kg = 0 for everything, multiplying by anything gives 0. The bug in the multiplication is invisible. **Fix: change the dataset or create a new one** with values that would produce different results between the buggy and fixed formula. Never skip verification with "the data doesn't show it".
|
|
1921
|
+
|
|
1922
|
+
### 2. Off-by-constant errors
|
|
1923
|
+
The bug formula (1+alpha) * prod * rate * T happens to equal the correct mass * rate when alpha=0 AND prod*T = mass. If your test input satisfies both, both formulas pass. **Fix: pick inputs where the two formulas diverge.**
|
|
1924
|
+
|
|
1925
|
+
### 3. Sign errors and unit mismatches
|
|
1926
|
+
mg/MJ vs kg/MWh differ by 10^9 / 3600 ≈ 277,777x. A "small refactor" can introduce this and pass type checks. **Fix: include unit conversion in the trivialized expected calc.**
|
|
1927
|
+
|
|
1928
|
+
### 4. The user testing loop
|
|
1929
|
+
"I'll fix it and the user will test it" is not a fix workflow — it's a hand-off of responsibility. Every numerical fix should come with at least one number you computed and one number the system produced.
|
|
1930
|
+
|
|
1931
|
+
## Status transitions
|
|
1932
|
+
|
|
1933
|
+
- After implementing the fix → status=ready_for_test, with --evidence describing what the fix does and what test should verify it
|
|
1934
|
+
- After running the verification → status=tested (req) or resolved (backlog), with --evidence containing the expected vs actual numbers
|
|
1935
|
+
|
|
1936
|
+
If the verification fails: do NOT mark tested. Go back to step 1, find the discrepancy, fix again.
|
|
1937
|
+
|
|
1938
|
+
## Cross-reference
|
|
1939
|
+
|
|
1940
|
+
- qa-evidence-first.md — the core principle
|
|
1941
|
+
- qa-developer.md — general implementation guidance
|
|
1942
|
+
- The CLI will refuse status=implemented/tested without --evidence (min 10 chars). Use that as your forcing function.
|
|
1722
1943
|
`
|
|
1723
1944
|
};
|
|
1724
1945
|
|
|
@@ -1732,6 +1953,7 @@ Say "I'm unsure" — that's better than guessing. Suggest what evidence would re
|
|
|
1732
1953
|
console.log(" ✓ qa-test-analyst.md");
|
|
1733
1954
|
console.log(" ✓ qa-release-manager.md");
|
|
1734
1955
|
console.log(" ✓ qa-evidence-first.md");
|
|
1956
|
+
console.log(" ✓ qa-numerical-verification.md");
|
|
1735
1957
|
|
|
1736
1958
|
// 4. Generate app context skill from API
|
|
1737
1959
|
try {
|