@biffo/cli 0.312.2 → 0.312.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.
@@ -109,6 +109,44 @@ jobs:
109
109
  - name: Scale drift guard
110
110
  if: ${{ !cancelled() }}
111
111
  run: pnpm exec biffo-scale-guard
112
+ # An unsuppressed `aws lambda update-function-*` prints the whole
113
+ # resolved function configuration -- every env var, plaintext -- into
114
+ # the Actions log, and the response is never consumed (biffo-template
115
+ # #334). deploy.yml's own `aws lambda update-function-code` call already
116
+ # follows the convention (--output text --query 'LastUpdateStatus'), but
117
+ # nothing in this repo's own CI asserted it would keep doing so: the
118
+ # guard existed only in biffo-template's root ci.yml, which scans this
119
+ # skeleton's tree from the template side but never runs inside a
120
+ # scaffolded sibling's own CI, so a regression added directly to a live
121
+ # sibling's deploy.yml was regression-proof only by a human reading the
122
+ # diff (#1968). `scripts/biffo.sh` resolves the version-pinned CLI this
123
+ # sibling was stamped with at birth (#1109), same as every other guard
124
+ # invocation in this job.
125
+ - name: Lambda output guard
126
+ if: ${{ !cancelled() }}
127
+ # This job's working-directory default is apps/frontend (top of this
128
+ # job), but scripts/biffo.sh lives at the repo root -- same relative-
129
+ # path convention as the js-audit job's own
130
+ # `sh ../../scripts/js-dependency-audit.sh` below. A bare
131
+ # `scripts/biffo.sh` here fails unconditionally with "No such file",
132
+ # on every run, regardless of whether there is a real
133
+ # lambda-output/terraform-input violation (biffo-template#1983).
134
+ run: sh ../../scripts/biffo.sh check lambda-output
135
+ # A Terraform subcommand that can prompt for an unresolved variable
136
+ # without -input=false, or a workflow running Terraform at all without
137
+ # TF_INPUT set as a backstop, hangs forever on a GitHub Actions runner --
138
+ # there is no stdin to answer the prompt with (biffo-template#322).
139
+ # `-auto-approve` does NOT prevent this. Same gap as the lambda-output
140
+ # guard above: deploy.yml's terraform invocations already pass
141
+ # `-input=false` and the workflow-level `TF_INPUT: '0'` above already
142
+ # exists as a backstop, but nothing in this repo's own CI asserted
143
+ # either would stay true (#1968).
144
+ - name: Terraform-input guard
145
+ if: ${{ !cancelled() }}
146
+ # Same cwd fix as the Lambda output guard step above -- this job's
147
+ # working-directory default is apps/frontend, and scripts/biffo.sh
148
+ # lives at the repo root (biffo-template#1983).
149
+ run: sh ../../scripts/biffo.sh check terraform-input
112
150
 
113
151
  e2e:
114
152
  name: E2E (Playwright)
package/dist/index.js CHANGED
@@ -2838,12 +2838,124 @@ function parseBodyChangeDeclaration(content) {
2838
2838
  }
2839
2839
  return { classification: value[1], reason: value[2] };
2840
2840
  }
2841
+ var BODY_CHANGE_LINE_ONLY_RE = /^[ \t]*# biffo:body-change:[ \t]*(.*)$/;
2842
+ function pythonStatementGroups(lines, docLineMask) {
2843
+ const groupId = new Array(lines.length);
2844
+ let depth = 0;
2845
+ let quote = null;
2846
+ let currentGroup = -1;
2847
+ for (let li = 0; li < lines.length; li++) {
2848
+ if (docLineMask[li]) {
2849
+ groupId[li] = currentGroup;
2850
+ continue;
2851
+ }
2852
+ if (depth === 0 && quote === null) currentGroup++;
2853
+ groupId[li] = currentGroup;
2854
+ const line = lines[li];
2855
+ for (let ci = 0; ci < line.length; ci++) {
2856
+ const ch = line[ci];
2857
+ if (quote) {
2858
+ if (ch === "\\") {
2859
+ ci++;
2860
+ continue;
2861
+ }
2862
+ if (ch === quote) quote = null;
2863
+ continue;
2864
+ }
2865
+ if (ch === '"' || ch === "'") {
2866
+ quote = ch;
2867
+ continue;
2868
+ }
2869
+ if (ch === "#") break;
2870
+ if (ch === "(" || ch === "[" || ch === "{") depth++;
2871
+ else if (ch === ")" || ch === "]" || ch === "}") depth = Math.max(0, depth - 1);
2872
+ }
2873
+ }
2874
+ return groupId;
2875
+ }
2876
+ var MAX_DIFF_CELLS = 4e3 * 4e3;
2877
+ function computeMatchedNewLineIndices(oldLines, newLines) {
2878
+ const n = oldLines.length;
2879
+ const m = newLines.length;
2880
+ if (n * m > MAX_DIFF_CELLS) return /* @__PURE__ */ new Set();
2881
+ const stride = m + 1;
2882
+ const dp = new Uint32Array((n + 1) * stride);
2883
+ for (let i2 = n - 1; i2 >= 0; i2--) {
2884
+ for (let j2 = m - 1; j2 >= 0; j2--) {
2885
+ dp[i2 * stride + j2] = oldLines[i2] === newLines[j2] ? dp[(i2 + 1) * stride + (j2 + 1)] + 1 : Math.max(dp[(i2 + 1) * stride + j2], dp[i2 * stride + (j2 + 1)]);
2886
+ }
2887
+ }
2888
+ const matchedNew = /* @__PURE__ */ new Set();
2889
+ let i = 0;
2890
+ let j = 0;
2891
+ while (i < n && j < m) {
2892
+ if (oldLines[i] === newLines[j]) {
2893
+ matchedNew.add(j);
2894
+ i++;
2895
+ j++;
2896
+ } else if (dp[(i + 1) * stride + j] >= dp[i * stride + (j + 1)]) {
2897
+ i++;
2898
+ } else {
2899
+ j++;
2900
+ }
2901
+ }
2902
+ return matchedNew;
2903
+ }
2904
+ function findEditScopedBodyChangeDeclarations(oldContent, newContent) {
2905
+ const oldLines = oldContent.split("\n");
2906
+ const newLines = newContent.split("\n");
2907
+ const matchedNew = computeMatchedNewLineIndices(oldLines, newLines);
2908
+ const newDocMask = pythonDocstringLineMask(newContent);
2909
+ const groupIds = pythonStatementGroups(newLines, newDocMask);
2910
+ const oldMarkerLines = new Set(oldLines.filter((line) => BODY_CHANGE_LINE_ONLY_RE.test(line)));
2911
+ const results = [];
2912
+ let i = 0;
2913
+ while (i < newLines.length) {
2914
+ const gid = groupIds[i];
2915
+ let end = i;
2916
+ while (end < newLines.length && groupIds[end] === gid) end++;
2917
+ let needsDeclaration = false;
2918
+ for (let k = i; k < end; k++) {
2919
+ if (!matchedNew.has(k) && isSubstantiveLine(newLines[k], newDocMask[k] ?? false)) {
2920
+ needsDeclaration = true;
2921
+ break;
2922
+ }
2923
+ }
2924
+ if (needsDeclaration) {
2925
+ const aboveIdx = i - 1;
2926
+ const above = aboveIdx >= 0 ? newLines[aboveIdx] : null;
2927
+ const aboveMatch = above !== null ? BODY_CHANGE_LINE_ONLY_RE.exec(above) : null;
2928
+ if (!aboveMatch) {
2929
+ results.push({ declaration: null, staleOnly: false });
2930
+ } else if (oldMarkerLines.has(above)) {
2931
+ results.push({ declaration: null, staleOnly: true });
2932
+ } else {
2933
+ const rest = (aboveMatch[1] ?? "").trim();
2934
+ const value = BODY_CHANGE_VALUE_RE.exec(rest);
2935
+ if (!value) {
2936
+ throw new Error(
2937
+ `Malformed ${BODY_CHANGE_MARKER} marker: expected "${BODY_CHANGE_MARKER} replay-safe \u2014 <reason>" or "${BODY_CHANGE_MARKER} outcome-changing \u2014 <reason>", got "${rest}".`
2938
+ );
2939
+ }
2940
+ results.push({
2941
+ declaration: {
2942
+ classification: value[1],
2943
+ reason: value[2]
2944
+ },
2945
+ staleOnly: false
2946
+ });
2947
+ }
2948
+ }
2949
+ i = end;
2950
+ }
2951
+ return results;
2952
+ }
2841
2953
  var DOCSTRING_OPEN_RE = /^[rRuU]?("""|''')/;
2842
2954
  var DEF_OR_CLASS_RE = /^(async\s+def|def|class)\b/;
2843
2955
  var HEADER_END_RE = /:\s*(#.*)?$/;
2844
- function stripPythonDocstrings(source) {
2956
+ function pythonDocstringLineMask(source) {
2845
2957
  const lines = source.split("\n");
2846
- const out = [];
2958
+ const mask = new Array(lines.length).fill(false);
2847
2959
  let expectDocstring = true;
2848
2960
  let inHeader = false;
2849
2961
  let i = 0;
@@ -2851,7 +2963,6 @@ function stripPythonDocstrings(source) {
2851
2963
  const line = lines[i];
2852
2964
  const trimmed = line.trim();
2853
2965
  if (trimmed === "" || trimmed.startsWith("#")) {
2854
- out.push(line);
2855
2966
  i++;
2856
2967
  continue;
2857
2968
  }
@@ -2859,13 +2970,20 @@ function stripPythonDocstrings(source) {
2859
2970
  if (open) {
2860
2971
  const delim = open[1];
2861
2972
  expectDocstring = false;
2973
+ mask[i] = true;
2862
2974
  if (trimmed.slice(open[0].length).includes(delim)) {
2863
2975
  i++;
2864
2976
  continue;
2865
2977
  }
2866
2978
  i++;
2867
- while (i < lines.length && !lines[i].includes(delim)) i++;
2868
- if (i < lines.length) i++;
2979
+ while (i < lines.length && !lines[i].includes(delim)) {
2980
+ mask[i] = true;
2981
+ i++;
2982
+ }
2983
+ if (i < lines.length) {
2984
+ mask[i] = true;
2985
+ i++;
2986
+ }
2869
2987
  continue;
2870
2988
  }
2871
2989
  if (inHeader || DEF_OR_CLASS_RE.test(trimmed)) {
@@ -2875,17 +2993,25 @@ function stripPythonDocstrings(source) {
2875
2993
  } else {
2876
2994
  expectDocstring = false;
2877
2995
  }
2878
- out.push(line);
2879
2996
  i++;
2880
2997
  }
2881
- return out.join("\n");
2998
+ return mask;
2882
2999
  }
2883
3000
  function migrationBodyHash(content) {
2884
- const normalised = stripPythonDocstrings(content).split("\n").map((line) => line.trimEnd()).filter(
2885
- (line) => !REVISION_RE.test(line) && !DOWN_REVISION_RE.test(line) && !line.trimStart().startsWith("#") && line !== ""
2886
- ).join("\n");
3001
+ const lines = content.split("\n");
3002
+ const docMask = pythonDocstringLineMask(content);
3003
+ const normalised = lines.filter((line, i) => isSubstantiveLine(line, docMask[i] ?? false)).map((line) => line.trimEnd()).join("\n");
2887
3004
  return createHash("sha256").update(normalised).digest("hex");
2888
3005
  }
3006
+ function isSubstantiveLine(rawLine, inDocstring) {
3007
+ if (inDocstring) return false;
3008
+ const line = rawLine.trimEnd();
3009
+ if (line === "") return false;
3010
+ if (line.trimStart().startsWith("#")) return false;
3011
+ if (REVISION_RE.test(line)) return false;
3012
+ if (DOWN_REVISION_RE.test(line)) return false;
3013
+ return true;
3014
+ }
2889
3015
  function migrationSlug(file) {
2890
3016
  return file.replace(/\.py$/, "").replace(/^[0-9a-f]+_/i, "");
2891
3017
  }
@@ -13457,6 +13583,7 @@ function checkMigrationBodyChangeMarkers(diffs) {
13457
13583
  const unchanged = [];
13458
13584
  const declared = [];
13459
13585
  const violations = [];
13586
+ let examinedChanged = 0;
13460
13587
  for (const d of diffs) {
13461
13588
  if (d.status === "added") {
13462
13589
  exemptAdded.push(d.file);
@@ -13471,24 +13598,39 @@ function checkMigrationBodyChangeMarkers(diffs) {
13471
13598
  unchanged.push(d.file);
13472
13599
  continue;
13473
13600
  }
13474
- let decl;
13601
+ examinedChanged++;
13602
+ let results;
13475
13603
  try {
13476
- decl = parseBodyChangeDeclaration(d.newContent ?? "");
13604
+ results = findEditScopedBodyChangeDeclarations(d.oldContent ?? "", d.newContent ?? "");
13477
13605
  } catch (err) {
13478
13606
  violations.push({ file: d.file, reason: err.message });
13479
13607
  continue;
13480
13608
  }
13481
- if (decl) {
13482
- declared.push({ file: d.file, classification: decl.classification });
13483
- } else {
13609
+ if (results.length === 0) {
13484
13610
  violations.push({
13485
13611
  file: d.file,
13486
- reason: `this edit changes the migration's hashed body (DDL, not just a docstring or comment) with no \`${BODY_CHANGE_MARKER}\` declaration.`
13612
+ reason: "this edit changes the migration's hashed body, but no specific statement could be isolated to require a declaration from \u2014 reported as undeclared rather than silently passed."
13487
13613
  });
13614
+ continue;
13615
+ }
13616
+ for (const result of results) {
13617
+ if (result.declaration) {
13618
+ declared.push({ file: d.file, classification: result.declaration.classification });
13619
+ } else if (result.staleOnly) {
13620
+ violations.push({
13621
+ file: d.file,
13622
+ reason: `this edit changes the migration's hashed body (DDL, not just a docstring or comment), and the only \`${BODY_CHANGE_MARKER}\` marker directly above it already existed, unchanged, before this edit \u2014 a stale declaration left over from a different change does not cover this one.`
13623
+ });
13624
+ } else {
13625
+ violations.push({
13626
+ file: d.file,
13627
+ reason: `this edit changes the migration's hashed body (DDL, not just a docstring or comment) with no \`${BODY_CHANGE_MARKER}\` declaration directly above it.`
13628
+ });
13629
+ }
13488
13630
  }
13489
13631
  }
13490
13632
  return {
13491
- examined: unchanged.length + declared.length + violations.length,
13633
+ examined: unchanged.length + examinedChanged,
13492
13634
  exemptAdded,
13493
13635
  unchanged,
13494
13636
  declared,
@@ -13612,6 +13754,10 @@ ${BOLD2}What to do${OFF2}
13612
13754
  converges it. Neither label is enforced yet (#751 is reporting-only, pending
13613
13755
  more examples) \u2014 but recording it now is what lets that decision be made
13614
13756
  later instead of never.
13757
+
13758
+ A marker left over from an earlier, already-merged edit to this same file
13759
+ does ${DIM2}not${OFF2} cover a new one \u2014 add a fresh marker beside THIS change,
13760
+ even if the classification happens to match (#751 precondition D).
13615
13761
  `);
13616
13762
  process.exit(1);
13617
13763
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.312.2",
3
+ "version": "0.312.4",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",