qfai 2.1.0 → 2.1.1

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 (124) hide show
  1. package/README.md +272 -20
  2. package/assets/defaults/workflows/change-tooling.yml +0 -4
  3. package/assets/init/.claude/settings.json +8 -8
  4. package/assets/init/.codex/hooks.json +6 -6
  5. package/assets/init/.qfai/assistant/agent/implementation-reviewer.md +14 -1
  6. package/assets/init/.qfai/assistant/agent/qa-gatekeeper.md +29 -0
  7. package/assets/init/.qfai/assistant/rule/constitution.md +5 -4
  8. package/assets/init/.qfai/assistant/rule/drift-protocol.md +14 -3
  9. package/assets/init/.qfai/assistant/rule/references/asset-authoring.md +70 -0
  10. package/assets/init/.qfai/assistant/rule/references/context-summary.md +30 -0
  11. package/assets/init/.qfai/assistant/rule/references/gate-failure-autorepair.md +42 -0
  12. package/assets/init/.qfai/assistant/rule/references/griller-recommendations.md +23 -0
  13. package/assets/init/.qfai/assistant/rule/references/host-backstops.md +60 -0
  14. package/assets/init/.qfai/assistant/rule/references/placeholder-scan.md +25 -0
  15. package/assets/init/.qfai/assistant/rule/references/reviewer-findings.md +41 -0
  16. package/assets/init/.qfai/assistant/rule/references/reviewer-remit.md +35 -0
  17. package/assets/init/.qfai/assistant/rule/shared-skill-delegation-baseline.md +26 -139
  18. package/assets/init/.qfai/assistant/rule/shared-skill-operating-baseline.md +6 -137
  19. package/assets/init/.qfai/assistant/rule/stage-cost.md +1 -1
  20. package/assets/init/.qfai/assistant/rule/test-layers.md +42 -0
  21. package/assets/init/.qfai/assistant/skill/qfai-configure/SKILL.md +8 -42
  22. package/assets/init/.qfai/assistant/skill/qfai-configure/references/principles.md +25 -0
  23. package/assets/init/.qfai/assistant/skill/qfai-discussion/SKILL.md +1 -1
  24. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/discussion-completion-matrix.md +9 -2
  25. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui_ux/platform_baselines.md +11 -0
  26. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui_ux/review_audit_playbook.md +11 -0
  27. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui_ux_best_practices.md +12 -36
  28. package/assets/init/.qfai/assistant/skill/qfai-grill/SKILL.md +1 -1
  29. package/assets/init/.qfai/assistant/skill/qfai-grilling/SKILL.md +16 -88
  30. package/assets/init/.qfai/assistant/skill/qfai-grilling/references/delegated-session.md +58 -0
  31. package/assets/init/.qfai/assistant/skill/qfai-grilling/references/session-record.md +42 -0
  32. package/assets/init/.qfai/assistant/skill/qfai-implement/SKILL.md +8 -1
  33. package/assets/init/.qfai/assistant/skill/qfai-implement/references/credential-reuse.md +9 -0
  34. package/assets/init/.qfai/assistant/skill/qfai-implement/references/cross-spec-ownership.md +41 -0
  35. package/assets/init/.qfai/assistant/skill/qfai-implement/references/oracle-strength.md +6 -1
  36. package/assets/init/.qfai/assistant/skill/qfai-implement/references/red-admissibility.md +1 -1
  37. package/assets/init/.qfai/assistant/skill/qfai-implement/references/ui-affecting.md +1 -3
  38. package/assets/init/.qfai/assistant/skill/qfai-maintain/SKILL.md +1 -1
  39. package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/SKILL.md +4 -4
  40. package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/references/migration-guide.md +19 -5
  41. package/assets/init/.qfai/assistant/skill/qfai-prototyping/SKILL.md +1 -1
  42. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/design-md-spec.md +14 -5
  43. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/generator-prompt.md +10 -0
  44. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/handoff.md +1 -1
  45. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/iteration-loop.md +3 -4
  46. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/review-payload-schema.md +10 -2
  47. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/reviewer-prompt.md +16 -3
  48. package/assets/init/.qfai/assistant/skill/qfai-run/SKILL.md +6 -21
  49. package/assets/init/.qfai/assistant/skill/qfai-run/references/extraction.md +30 -10
  50. package/assets/init/.qfai/assistant/skill/qfai-run/references/operator-screens.md +34 -0
  51. package/assets/init/.qfai/assistant/skill/qfai-run/references/plan.md +28 -5
  52. package/assets/init/.qfai/assistant/skill/qfai-run/references/stage-points.md +29 -0
  53. package/assets/init/.qfai/assistant/skill/qfai-sdd/SKILL.md +1 -1
  54. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/concrete-abstract-cycle.md +10 -1
  55. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/contract-artifact-rules.md +21 -9
  56. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/design-anti-patterns.md +19 -2
  57. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/design-md-authoring.md +11 -5
  58. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/design-md-brand-catalog.md +17 -2
  59. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/requirements-decomposition.md +2 -2
  60. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-execution-playbook.md +5 -5
  61. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-phase-checklists.md +4 -4
  62. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-quality-gate.md +2 -2
  63. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-triage.md +47 -5
  64. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/spec-traceability-rules.md +15 -3
  65. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/ui-contract-guide.md +16 -1
  66. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/ui-design-contract-normalization.md +1 -2
  67. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/change-request.md +2 -2
  68. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/contracts/db-contract.sample.sql +2 -2
  69. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/spec/03_contract/cli/cli-NNNN-title.md +2 -2
  70. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/spec/03_contract/tech.md +1 -1
  71. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/spec/decisions.md +4 -0
  72. package/assets/init/.qfai/assistant/skill/qfai-triage/SKILL.md +1 -1
  73. package/assets/init/.qfai/assistant/skill/qfai-verify/SKILL.md +1 -1
  74. package/assets/init/.qfai/assistant/skill/qfai-verify/references/verify-output-contract.md +22 -8
  75. package/assets/init/.qfai/assistant/skill/web-research/SKILL.md +2 -10
  76. package/assets/init/.qfai/assistant/step/common-design-md/STEP.md +17 -3
  77. package/assets/init/.qfai/assistant/step/common-gate-run/STEP.md +30 -7
  78. package/assets/init/.qfai/assistant/step/discussion-uiux/STEP.md +11 -1
  79. package/assets/init/.qfai/assistant/step/implement-dep-bump/STEP.md +7 -0
  80. package/assets/init/.qfai/assistant/step/implement-scaffold/STEP.md +3 -3
  81. package/assets/init/.qfai/assistant/step/implement-seam/STEP.md +3 -1
  82. package/assets/init/.qfai/assistant/step/implement-tdd/STEP.md +3 -1
  83. package/assets/init/.qfai/assistant/step/implement-test-fix/STEP.md +2 -0
  84. package/assets/init/.qfai/assistant/step/sdd-flow/STEP.md +2 -0
  85. package/assets/init/.qfai/assistant/step/sdd-gate/STEP.md +4 -0
  86. package/assets/init/.qfai/assistant/step/sdd-triage/STEP.md +7 -3
  87. package/assets/init/.qfai/assistant/step/verify-change-note/STEP.md +8 -5
  88. package/assets/init/.qfai/assistant/step/verify-commit/STEP.md +6 -1
  89. package/assets/init/.qfai/assistant/step/verify-qfai-gate/STEP.md +20 -3
  90. package/assets/init/.qfai/assistant/step/verify-repeat-run/STEP.md +1 -1
  91. package/assets/init/.qfai/assistant/step/verify-repo-gate/STEP.md +23 -6
  92. package/assets/init/.qfai/waivers.yml +1 -1
  93. package/assets/init/root/.agents/rules/ai-readable-markdown.md +98 -0
  94. package/assets/init/root/.agents/rules/api-budget.md +4 -5
  95. package/assets/init/root/.agents/rules/documentation-clarity.md +30 -2
  96. package/assets/init/root/.agents/rules/grilling.md +4 -4
  97. package/assets/init/root/.agents/rules/interface-clarity.md +5 -5
  98. package/assets/init/root/.agents/rules/minimal-implementation.md +8 -0
  99. package/assets/init/root/.agents/rules/reminders.json +2 -2
  100. package/assets/init/root/.agents/rules/temporary-files.md +14 -5
  101. package/assets/init/root/.agents/rules/untrusted-content.md +3 -3
  102. package/assets/init/root/.agents/rules/user-questions.md +34 -7
  103. package/assets/init/root/.gitattributes +9 -0
  104. package/assets/init/root/.github/workflows/qfai-docs.yml +43 -11
  105. package/assets/init/root/.github/workflows/qfai-tests.yml +58 -15
  106. package/assets/init/root/.github/workflows/qfai-validate.yml +49 -42
  107. package/assets/init/root/AGENTS.md +1 -0
  108. package/assets/init/root/CLAUDE.md +1 -0
  109. package/assets/mdschema/story/03_contract/tech.mdschema.yml +7 -3
  110. package/assets/sandbox-templates/default-deny.yaml +0 -2
  111. package/assets/schemas/workflow/plan.schema.json +9 -2
  112. package/assets/scripts/check-mdschema.mjs +172 -5
  113. package/dist/cli/index.cjs +3979 -3055
  114. package/dist/cli/index.cjs.map +1 -1
  115. package/dist/cli/index.mjs +3167 -2243
  116. package/dist/cli/index.mjs.map +1 -1
  117. package/dist/index.cjs +3024 -2500
  118. package/dist/index.cjs.map +1 -1
  119. package/dist/index.d.cts +23 -9
  120. package/dist/index.d.ts +23 -9
  121. package/dist/index.mjs +2881 -2358
  122. package/dist/index.mjs.map +1 -1
  123. package/docs/MIGRATION-2.0.0.md +1 -1
  124. package/package.json +3 -3
@@ -85,6 +85,7 @@
85
85
  * node scripts/check-mdschema.mjs --scope all --summary
86
86
  * node scripts/check-mdschema.mjs --base <ref> # ratchet against <ref>
87
87
  * node scripts/check-mdschema.mjs --scope files a.md b.md
88
+ * node scripts/check-mdschema.mjs --scope all --show-patterns # the pattern behind each forbidden-text finding
88
89
  * node scripts/check-mdschema.mjs --root <dir> --scope all # another tree
89
90
  * node scripts/check-mdschema.mjs --tools <dir> --scope all # mdschema installed under <dir>
90
91
  *
@@ -223,6 +224,27 @@ function mdschemaEntryPoint(packageDir) {
223
224
  return existsSync(entry) ? entry : null;
224
225
  }
225
226
 
227
+ /**
228
+ * The text of a double-quoted YAML scalar, with its escapes decoded the way the
229
+ * configuration loader's YAML parser decodes them, so `"docs\tree"` names the
230
+ * directory the loader reads and not `docs/tree`.
231
+ *
232
+ * SIMPLIFIED: decodes the escapes JSON shares with YAML and keeps any other
233
+ * escape as written.
234
+ * Lift when: a configuration spells a path with an escape only YAML has, such
235
+ * as `\x41` or `\_`.
236
+ *
237
+ * @param {string} body The text between the quotes.
238
+ * @returns {string}
239
+ */
240
+ function decodeDoubleQuoted(body) {
241
+ try {
242
+ return JSON.parse(`"${body}"`);
243
+ } catch {
244
+ return body;
245
+ }
246
+ }
247
+
226
248
  /**
227
249
  * Reads one directory under `paths` out of `qfai.config.yaml`.
228
250
  *
@@ -255,13 +277,16 @@ function readConfiguredDir(root, key, fallback) {
255
277
  // to the block so an unrelated key under another mapping cannot win.
256
278
  const block = /^paths:[ \t]*$([\s\S]*?)^(?=\S)/m.exec(`${text}\n￿`);
257
279
  const scope = block === null ? text : block[1];
258
- const found = new RegExp(`^[ \\t]+${key}:[ \\t]*["']?([^"'\\r\\n#]+)["']?[ \\t]*$`, "m").exec(
259
- scope,
260
- );
280
+ const found = new RegExp(
281
+ `^[ \\t]+${key}:[ \\t]*(?:"((?:[^"\\\\\\r\\n]|\\\\.)*)"|'([^'\\r\\n]*)'|([^"'\\r\\n#]+))[ \\t]*$`,
282
+ "m",
283
+ ).exec(scope);
261
284
  if (found === null) {
262
285
  return fallback;
263
286
  }
264
- const value = found[1].trim();
287
+ const value = (
288
+ found[1] === undefined ? (found[2] ?? found[3]) : decodeDoubleQuoted(found[1])
289
+ ).trim();
265
290
  if (value === "") {
266
291
  return fallback;
267
292
  }
@@ -726,10 +751,11 @@ function runMdschema(mdschema, schemaPath, files, root) {
726
751
  output: result.error instanceof Error ? result.error.message : "mdschema did not run",
727
752
  };
728
753
  }
754
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trimEnd();
729
755
  return {
730
756
  ok: result.status === 0,
731
757
  spawnFailed: false,
732
- output: `${result.stdout ?? ""}${result.stderr ?? ""}`.trimEnd(),
758
+ output: showPatterns ? output : explainForbiddenText(output, readFileSync(schemaPath, "utf-8")),
733
759
  };
734
760
  }
735
761
 
@@ -877,6 +903,143 @@ function documentUniverse(root, paths) {
877
903
  ];
878
904
  }
879
905
 
906
+ /**
907
+ * Whether a finding names the pattern that failed, as `mdschema` prints it,
908
+ * rather than the rule the pattern enforces. Set by `--show-patterns`, for the
909
+ * author of a schema.
910
+ */
911
+ let showPatterns = false;
912
+
913
+ /** A forbidden-text finding: the pattern that matched and the section it matched in. */
914
+ const FORBIDDEN_TEXT_MESSAGE = /^Forbidden text '.*' found in section '([^']*)'$/u;
915
+
916
+ /**
917
+ * The heading entries of a schema with the description each declares.
918
+ *
919
+ * Read with a line scanner, for the reason {@link rootHeadingPattern} is: the
920
+ * schemas are fixed shapes authored here, and this script runs before anything
921
+ * beyond the root devDependencies is installed. A description is a plain value
922
+ * or a folded or literal block.
923
+ *
924
+ * @param {string} schemaText
925
+ * @returns {{ pattern: string, regex: boolean, description: string }[]}
926
+ */
927
+ export function sectionDescriptions(schemaText) {
928
+ const lines = schemaText.split(/\r?\n/);
929
+ const entries = [];
930
+ let current = null;
931
+ for (let i = 0; i < lines.length; i++) {
932
+ const raw = lines[i];
933
+ if (/^\s*-\s+heading:\s*$/.test(raw)) {
934
+ current = { pattern: "", regex: false, description: "" };
935
+ entries.push(current);
936
+ continue;
937
+ }
938
+ if (current === null) continue;
939
+ const pattern = /^\s+pattern:\s*(?:"([^"]*)"|'([^']*)'|(\S+))\s*$/.exec(raw);
940
+ if (pattern !== null && current.pattern === "") {
941
+ current.pattern = pattern[1] ?? pattern[2] ?? pattern[3] ?? "";
942
+ current.regex = /^\s+regex:\s*true\s*$/.test(lines[i + 1] ?? "");
943
+ continue;
944
+ }
945
+ const description = /^(\s*)description:\s*(.*?)\s*$/.exec(raw);
946
+ if (description === null || current.description !== "") continue;
947
+ const value = description[2];
948
+ if (/^[>|][+-]?$/.test(value)) {
949
+ const indent = description[1].length;
950
+ const block = [];
951
+ for (let j = i + 1; j < lines.length; j++) {
952
+ const next = lines[j];
953
+ if (next.trim() !== "" && next.length - next.trimStart().length <= indent) break;
954
+ block.push(next.trim());
955
+ }
956
+ current.description = block.join(" ").trim();
957
+ } else {
958
+ current.description = value.replace(/^(["'])(.*)\1$/u, "$2");
959
+ }
960
+ }
961
+ return entries;
962
+ }
963
+
964
+ /**
965
+ * What the schema says the section named `name` holds, or `null`.
966
+ *
967
+ * @param {{ pattern: string, regex: boolean, description: string }[]} entries
968
+ * @param {string} name the heading text, without its `#` marks
969
+ * @returns {string | null}
970
+ */
971
+ function sectionDescription(entries, name) {
972
+ for (const entry of entries) {
973
+ if (entry.description === "") continue;
974
+ for (let level = 1; level <= 6; level++) {
975
+ const heading = `${"#".repeat(level)} ${name}`;
976
+ let matches;
977
+ try {
978
+ matches = entry.regex
979
+ ? new RegExp(entry.pattern, "u").test(heading)
980
+ : entry.pattern === heading;
981
+ } catch {
982
+ matches = false;
983
+ }
984
+ if (matches) return entry.description;
985
+ }
986
+ }
987
+ return null;
988
+ }
989
+
990
+ /**
991
+ * Rewrites the forbidden-text findings in one batch of `mdschema check` output
992
+ * so each says what its section holds, in the schema's own words, instead of
993
+ * the pattern that failed.
994
+ *
995
+ * Patterns that fail at one position of one document are one finding: the
996
+ * reader fixes a line, not a pattern. A finding in a section whose schema gives
997
+ * no description keeps the pattern, because it is all there is to say. The
998
+ * closing tally follows the findings that remain.
999
+ *
1000
+ * @param {string} output
1001
+ * @param {string} schemaText
1002
+ * @returns {string}
1003
+ */
1004
+ export function explainForbiddenText(output, schemaText) {
1005
+ const entries = sectionDescriptions(schemaText);
1006
+ const lines = [];
1007
+ let seen = new Set();
1008
+ let merged = 0;
1009
+ for (const line of output.split(/\r?\n/)) {
1010
+ if (/^\S/.test(line)) seen = new Set();
1011
+ const found = VIOLATION_LINE.exec(line);
1012
+ const forbidden =
1013
+ found !== null && found[3] === "forbidden-text"
1014
+ ? FORBIDDEN_TEXT_MESSAGE.exec(found[4])
1015
+ : null;
1016
+ const description = forbidden === null ? null : sectionDescription(entries, forbidden[1]);
1017
+ if (found === null || forbidden === null || description === null) {
1018
+ lines.push(line);
1019
+ continue;
1020
+ }
1021
+ const key = `${found[1]}:${found[2]}:${forbidden[1]}`;
1022
+ if (seen.has(key)) {
1023
+ merged++;
1024
+ continue;
1025
+ }
1026
+ seen.add(key);
1027
+ lines.push(
1028
+ ` ✗ ${found[1]}:${found[2]} [forbidden-text] Section '${forbidden[1]}' does not hold what the schema requires: ${description}`,
1029
+ );
1030
+ }
1031
+ return lines
1032
+ .map((line) =>
1033
+ merged === 0
1034
+ ? line
1035
+ : line.replace(
1036
+ /^(\S+\s+Found )(\d+)( violation)/u,
1037
+ (_all, head, count, tail) => `${head}${Number(count) - merged}${tail}`,
1038
+ ),
1039
+ )
1040
+ .join("\n");
1041
+ }
1042
+
880
1043
  /** One violation line of `mdschema check` output: ` ✗ 3:4 [rule] message`. */
881
1044
  const VIOLATION_LINE = /^\s+✗\s+(\d+):(\d+)\s+\[([^\]]+)\]\s+(.*\S)\s*$/u;
882
1045
 
@@ -1056,6 +1219,10 @@ function parseArgs(argv) {
1056
1219
  summary = true;
1057
1220
  continue;
1058
1221
  }
1222
+ if (arg === "--show-patterns") {
1223
+ showPatterns = true;
1224
+ continue;
1225
+ }
1059
1226
  if (arg.startsWith("-")) {
1060
1227
  console.error(`check-mdschema: unknown flag ${arg}`);
1061
1228
  return 2;