qatools-cli 1.1.2 → 1.1.4

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/qatools.js +136 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qatools-cli",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "CLI for QA Tools — requirements, epics, sprints, sessions, App Context sync, project agenda. The companion to qa-tools.com.",
5
5
  "bin": {
6
6
  "qatools": "./qatools.js"
package/qatools.js CHANGED
@@ -903,6 +903,14 @@ program
903
903
  next: `qatools requirement-update -i <id> --status approved (or refine AC first)`,
904
904
  });
905
905
  }
906
+ if (data.agenda.accessModelMissing) {
907
+ sections.push({
908
+ icon: "⚪",
909
+ title: "No access model defined for this project",
910
+ items: [" • Who can sign in? Which roles exist? What's protected? How is data isolated?"],
911
+ next: `qatools access show (shows a minimal example) → qatools access set -f model.json`,
912
+ });
913
+ }
906
914
 
907
915
  if (sections.length === 0) {
908
916
  console.log(" ✓ Nothing on the agenda. The project is in a clean state.\n");
@@ -1370,6 +1378,116 @@ program
1370
1378
  process.exit(0);
1371
1379
  });
1372
1380
 
1381
+ // access subcommands grouped under 'access'
1382
+ const accessCmd = program
1383
+ .command("access")
1384
+ .description("Manage the project's Access Model (who-can-do-what, structured)");
1385
+
1386
+ accessCmd
1387
+ .command("show")
1388
+ .description("Show the access model for the project")
1389
+ .option("--project <id>", "Target project ID (overrides default)")
1390
+ .option("-o, --output <path>", "Save to JSON file")
1391
+ .option("--json", "Output JSON to stdout")
1392
+ .option("-e, --email <email>", "Login email")
1393
+ .option("-p, --password <password>", "Login password")
1394
+ .action(async (options) => {
1395
+ await authenticate(options);
1396
+ const creds = getCredentials();
1397
+ const projectId = options.project || creds?.projectId;
1398
+ if (!projectId) { console.error("No project configured. Run: qatools setup, or pass --project <id>"); process.exit(1); }
1399
+ const getModel = httpsCallable(functions, "getAccessModel");
1400
+ let result;
1401
+ try { result = await getModel({ projectId }); }
1402
+ catch (e) { console.error("Failed:", e.message); process.exit(1); }
1403
+ const model = result.data.accessModel;
1404
+ if (!model) {
1405
+ console.log(`No access model defined for this project.`);
1406
+ console.log(`Define one by writing a JSON file and running: qatools access set -f model.json`);
1407
+ console.log(``);
1408
+ console.log(`Minimal shape:`);
1409
+ console.log(JSON.stringify({
1410
+ authMethods: [{ key: "google", description: "Google OAuth", primary: true }],
1411
+ roles: [{ key: "owner", description: "Project owner with full control" }],
1412
+ resources: [{ key: "project", description: "Project metadata", ownedBy: "project", permissions: [{ role: "owner", actions: ["read","create","update","delete"] }] }],
1413
+ isolationRules: [{ scope: "project", description: "Data scoped by projectId field", enforcedBy: ["firestore.rules", "checkProjectAccess()"] }],
1414
+ }, null, 2));
1415
+ process.exit(0);
1416
+ }
1417
+ if (options.output) {
1418
+ fs.writeFileSync(path.resolve(options.output), JSON.stringify(model, null, 2));
1419
+ console.log(`Saved to ${options.output}`);
1420
+ process.exit(0);
1421
+ }
1422
+ if (options.json) {
1423
+ console.log(JSON.stringify(model, null, 2));
1424
+ process.exit(0);
1425
+ }
1426
+ // Pretty text output
1427
+ console.log(``);
1428
+ console.log(`Access Model — project ${projectId}`);
1429
+ console.log(``);
1430
+ if (model.authMethods?.length) {
1431
+ console.log(`AUTH METHODS`);
1432
+ for (const a of model.authMethods) console.log(` ${a.primary ? "*" : " "} ${a.key.padEnd(15)} ${a.description}`);
1433
+ console.log(``);
1434
+ }
1435
+ if (model.roles?.length) {
1436
+ console.log(`ROLES`);
1437
+ for (const r of model.roles) {
1438
+ const inh = r.inherits?.length ? ` (inherits: ${r.inherits.join(", ")})` : "";
1439
+ console.log(` ${r.key.padEnd(15)} ${r.description}${inh}`);
1440
+ }
1441
+ console.log(``);
1442
+ }
1443
+ if (model.resources?.length) {
1444
+ console.log(`RESOURCES`);
1445
+ for (const r of model.resources) {
1446
+ const coll = r.collection ? ` [${r.collection}]` : "";
1447
+ console.log(` ${r.key}${coll} — owned by ${r.ownedBy}`);
1448
+ console.log(` ${r.description}`);
1449
+ for (const p of (r.permissions || [])) {
1450
+ const cond = p.condition ? ` (${p.condition})` : "";
1451
+ console.log(` ${p.role.padEnd(15)} ${p.actions.join(",")}${cond}`);
1452
+ }
1453
+ }
1454
+ console.log(``);
1455
+ }
1456
+ if (model.isolationRules?.length) {
1457
+ console.log(`ISOLATION RULES`);
1458
+ for (const i of model.isolationRules) {
1459
+ console.log(` [${i.scope}] ${i.description}`);
1460
+ if (i.enforcedBy?.length) console.log(` enforced by: ${i.enforcedBy.join(", ")}`);
1461
+ }
1462
+ console.log(``);
1463
+ }
1464
+ process.exit(0);
1465
+ });
1466
+
1467
+ accessCmd
1468
+ .command("set")
1469
+ .description("Upload (replace) the entire access model from a JSON file")
1470
+ .requiredOption("-f, --file <path>", "Path to access model JSON")
1471
+ .option("--project <id>", "Target project ID (overrides default)")
1472
+ .option("-e, --email <email>", "Login email")
1473
+ .option("-p, --password <password>", "Login password")
1474
+ .action(async (options) => {
1475
+ await authenticate(options);
1476
+ const creds = getCredentials();
1477
+ const projectId = options.project || creds?.projectId;
1478
+ if (!projectId) { console.error("No project configured. Run: qatools setup, or pass --project <id>"); process.exit(1); }
1479
+ const model = JSON.parse(fs.readFileSync(path.resolve(options.file), "utf8"));
1480
+ const saveModel = httpsCallable(functions, "saveAccessModel");
1481
+ try {
1482
+ const result = await saveModel({ projectId, model });
1483
+ console.log(`Access model saved (${result.data.id}) — project ${projectId}`);
1484
+ } catch (e) {
1485
+ console.error("Failed:", e.message);
1486
+ process.exit(1);
1487
+ }
1488
+ process.exit(0);
1489
+ });
1490
+
1373
1491
  program
1374
1492
  .command("epics")
1375
1493
  .description("List epics for the project")
@@ -1863,6 +1981,12 @@ Every claim, verdict, analysis, and recommendation MUST cite specific evidence:
1863
1981
 
1864
1982
  ## If you're unsure
1865
1983
  Say "I'm unsure" — that's better than guessing. Suggest what evidence would resolve the uncertainty.
1984
+
1985
+ ## Specialized skills
1986
+
1987
+ 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".
1988
+
1989
+ 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.
1866
1990
  `,
1867
1991
  "qa-numerical-verification.md": `---
1868
1992
  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".
@@ -1874,6 +1998,18 @@ Most evidence-first examples deal with structural bugs (NullReferenceException,
1874
1998
 
1875
1999
  This skill is for those bugs. The verification pattern is non-negotiable.
1876
2000
 
2001
+ ## Before you start — entry-point checklist
2002
+
2003
+ Stop here and answer these BEFORE writing any verification code:
2004
+
2005
+ - [ ] Which input or test case actually triggers the bug? (Not "the system" — name a specific input.)
2006
+ - [ ] 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.)
2007
+ - [ ] If your input is non-discriminating: change it, or pick a different input. Never proceed with "the data doesn't show it".
2008
+ - [ ] 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").
2009
+ - [ ] 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.
2010
+
2011
+ If any answer is "I don't know", stop and ask. Don't guess.
2012
+
1877
2013
  ## The pattern
1878
2014
 
1879
2015
  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.