rcf-lite 0.32.2 → 0.32.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/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.32.3] - 2026-10-07
10
+
11
+ 0.32.3 is a hotfix for the 0.32.2 D6 consistency:unsatisfiable regression discovered by Barry on ai-on-record (define/kickoff, head 974b4c1) and second-sourced by Dex on WESPA (26 green-to-red findings in three classes, 2026-10-06 20:05Z). The regression came from pattern 4 of `extractThenFieldTokens` in `src/query/gates.js`, added in PR 313 as part of the issue 311 fix. breaking: false; the 0.32.1 and 0.32.2 fixes carry forward unchanged. minAgentAction: null.
12
+
13
+ ### Fixed
14
+
15
+ - **D6 consistency:unsatisfiable stops reading filename stems, non-shape PascalCase dot phrases and sentence-ending dots as record-field references (issue 315, w-2026-10-06-dave-059).** 0.32.2 added a pattern 4 to `extractThenFieldTokens` that fired `consistency:unsatisfiable` on any `<PascalCase>.<lowercaseWord>` match in an AC `then` clause. On ai-on-record define/kickoff that read `CLAUDE.md`, `AGENTS.md`, `rcf/evals/corpus-V.json`, `rcf/evals/golden-transcripts-V.json` and other filename stems as record fields and false-failed AC-1501-9:md, AC-2201-1:json, AC-2201-5:json and AC-2201-8:json. Dex's WESPA re-run on 2026-10-06 20:05Z (AI-1987) added 26 findings in three classes: filename stems with a filename-stem head (js x7, vue x5, md x1), PascalCase heads that are not recordShape names in the tree (`Math.random`, `TEST_USERS.admin`, `ToolExecutionContext.pushLlmCall`, `LoadedFilterSchema.distinctLiveSourceTables`, and backticked dot-phrases like `` `Something.listModelInfo` ``, `` `Something.userID` ``), and AC-130-1 producing the token `the` from a sentence-ending dot inside a dot-separated phrase (`Foo.the`). Fix: pattern 4 is now gated on the head to the left of the dot resolving to a recordShape name declared somewhere in the tree. `collectRecordShapeNames` walks the TAC interfaces alongside the existing `collectRecordShapeFields` and the resulting shape-name set is threaded through `extractThenFieldTokens` as a second argument. Three belt-and-braces guards stay in behind the shape-name gate so a hostile shape-name collision cannot reintroduce the class: an English-stop-word skip on the field token (so `Foo.the` is silent even if a shape named `Foo` were declared), a closed file-extension skip (md, json, jsonl, html, css, js, mjs, cjs, ts, vue, yaml, yml, txt, csv, pdf, png, jpg, svg, zip, wav, mp4, mov, log) and a two-char lookbehind path-context skip (preceded by `/` or `./`). Patterns 1 (narrowed backticked identifier), 2 (`<name> field`), 3 (`field: <name>`), the issue 311 fixtures and the AC-12113-2 class are preserved. Regression fixtures in `test/query/gates.test.js` cover each of the three new classes plus a positive control (`Session.lastSeenAt` where Session is declared without `lastSeenAt`) and a verbatim quote of the four ai-on-record ACs at head 974b4c1 as negative controls. Expected drop on WESPA: 26 unsatisfiable findings removed; the issue 311 drop and all AC-12113-2 class failures remain.
16
+
9
17
  ## [0.32.2] - 2026-10-06
10
18
 
11
19
  0.32.2 ships two Dex-lane fixes merged since 0.32.1 (merge b478a4e) as the WESPA readiness alignment release. D6 consistency:unsatisfiable now context-gates backticked identifiers in AC then-clauses so enum values, query-string parameter names, sort and filter literals, env-var names and config knobs stop being read as record-shape field names, and `rcf discover req-baseline opt-out` and `rcf define ledger concerns add` honour `--dry-run` by writing nothing. breaking: false; no behavioural changes on verbs consumers run in CI; fixes only. minAgentAction: null; the managed agent-instructions block hash did not move.
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "registerCanary": [
3
3
  {
4
- "id": "rc-2026-10-06-001",
5
- "createdAt": "2026-10-06T19:49:58.281Z",
6
- "buildVersion": "0.32.2-mockdriver",
4
+ "id": "rc-2026-10-07-001",
5
+ "createdAt": "2026-10-07T10:44:10.684Z",
6
+ "buildVersion": "0.32.3-mockdriver",
7
7
  "fixturePromptId": "canary-prompt-01",
8
8
  "responseWordCount": 55,
9
9
  "grades": {
@@ -34,9 +34,9 @@
34
34
  "shipDespiteFailReason": "mock canary driver used; no real subagent was dispatched. This record verifies canary infrastructure only, not the register itself."
35
35
  },
36
36
  {
37
- "id": "rc-2026-10-06-002",
38
- "createdAt": "2026-10-06T19:49:58.282Z",
39
- "buildVersion": "0.32.2-mockdriver",
37
+ "id": "rc-2026-10-07-002",
38
+ "createdAt": "2026-10-07T10:44:10.684Z",
39
+ "buildVersion": "0.32.3-mockdriver",
40
40
  "fixturePromptId": "canary-prompt-02",
41
41
  "responseWordCount": 55,
42
42
  "grades": {
@@ -67,9 +67,9 @@
67
67
  "shipDespiteFailReason": "mock canary driver used; no real subagent was dispatched. This record verifies canary infrastructure only, not the register itself."
68
68
  },
69
69
  {
70
- "id": "rc-2026-10-06-003",
71
- "createdAt": "2026-10-06T19:49:58.282Z",
72
- "buildVersion": "0.32.2-mockdriver",
70
+ "id": "rc-2026-10-07-003",
71
+ "createdAt": "2026-10-07T10:44:10.684Z",
72
+ "buildVersion": "0.32.3-mockdriver",
73
73
  "fixturePromptId": "canary-prompt-03",
74
74
  "responseWordCount": 55,
75
75
  "grades": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rcf-lite",
3
- "version": "0.32.2",
3
+ "version": "0.32.3",
4
4
  "type": "module",
5
5
  "description": "One-install tooling for the Requirements Confidence Framework (RCF): the unified `rcf` CLI grouped into the five RCF tool groups (discover, define, build, verify, audit) plus a small core set (init, doctor, guidance, mcp), an MCP server, the live tree viewer and the fresh-context adversarial ship-gate verifier. Consumes @stravica-ai/rcf-schemas.",
6
6
  "license": "Apache-2.0",
@@ -40,8 +40,16 @@
40
40
  # `npm install rcf-lite`.
41
41
 
42
42
  feedVersion: 1
43
- latest: "0.32.2"
43
+ latest: "0.32.3"
44
44
  releases:
45
+ - version: "0.32.3"
46
+ date: "2026-10-07"
47
+ breaking: false
48
+ headlines:
49
+ - "rcf-lite 0.32.3 is a hotfix for the 0.32.2 D6 consistency:unsatisfiable regression on filename stems and non-shape dot phrases (issue 315). Pattern 4 of extractThenFieldTokens now emits a field token only when the head to the left of the dot is a declared recordShape name in the tree, so AC then-clauses that mention report.json, CLAUDE.md, scripts/seeds/README.md, Math.random, TEST_USERS.admin, ToolExecutionContext.pushLlmCall and sentence-ending dots like Foo.the stop reading as record-field references; file-extension, path-context and English-stop-word skips stay as belt and braces. The 0.32.1 and 0.32.2 fixes carry forward unchanged."
50
+ minAgentAction: null
51
+ notesUrl: "https://stravica.ai/docs/rcf/changelog/"
52
+
45
53
  - version: "0.32.2"
46
54
  date: "2026-10-06"
47
55
  breaking: false
@@ -1823,6 +1823,7 @@ export function checkD6Consistency(ctx) {
1823
1823
  // `[draft]` recordShape counts as defined (the engineer owns the
1824
1824
  // draft; its `fields:` list is honest about what the then may name).
1825
1825
  const definedFields = collectRecordShapeFields(tree);
1826
+ const definedShapes = collectRecordShapeNames(tree);
1826
1827
  /** @type {Array<{ id: string, why: string }>} */
1827
1828
  const unsatisfiableFindings = [];
1828
1829
  let totalThenFieldChecks = 0;
@@ -1831,7 +1832,7 @@ export function checkD6Consistency(ctx) {
1831
1832
  for (const ac of acs) {
1832
1833
  const parts = parseWhenThen(ac?.description);
1833
1834
  if (!parts) continue;
1834
- const fields = extractThenFieldTokens(parts.then);
1835
+ const fields = extractThenFieldTokens(parts.then, definedShapes);
1835
1836
  for (const field of fields) {
1836
1837
  totalThenFieldChecks += 1;
1837
1838
  if (definedFields.has(field)) continue;
@@ -2028,6 +2029,42 @@ function collectRecordShapeFields(tree) {
2028
2029
  return out;
2029
2030
  }
2030
2031
 
2032
+ /**
2033
+ * Collect every recordShape interface name declared anywhere in the
2034
+ * tree. Pattern 4 of `extractThenFieldTokens` (dot-notation
2035
+ * `<Head>.<field>`) uses this set to gate: a Head that is not a
2036
+ * declared shape name is not read as a record-field reference. This
2037
+ * closes the 0.32.2 class in issue 315 where filename stems, non-shape
2038
+ * PascalCase heads (Math.random, TEST_USERS.admin,
2039
+ * ToolExecutionContext.pushLlmCall) and sentence-ending dots (Foo.the)
2040
+ * were being read as record-field references.
2041
+ *
2042
+ * @param {import('#core/store/walker.js').TreeModel} tree
2043
+ * @returns {Set<string>}
2044
+ */
2045
+ function collectRecordShapeNames(tree) {
2046
+ const out = new Set();
2047
+ for (const tac of tree.tacs ?? []) {
2048
+ for (const iface of tac.interfaces ?? []) {
2049
+ if (iface?.kind !== 'recordShape') continue;
2050
+ const name = iface?.name;
2051
+ if (typeof name !== 'string' || name.length === 0) continue;
2052
+ out.add(name);
2053
+ }
2054
+ }
2055
+ return out;
2056
+ }
2057
+
2058
+ // Closed list of file-extension tokens; pattern 4 (dot notation) never
2059
+ // emits a field token drawn from this set. Belt and braces behind the
2060
+ // shape-name gate: a tree with a recordShape literally named `Json` or
2061
+ // `Md` would still not read `foo.json`/`CLAUDE.md` as a field.
2062
+ const PATTERN_4_FILE_EXTENSION_SKIP = new Set([
2063
+ 'md', 'json', 'jsonl', 'html', 'css', 'js', 'mjs', 'cjs', 'ts', 'vue',
2064
+ 'yaml', 'yml', 'txt', 'csv', 'pdf', 'png', 'jpg', 'svg', 'zip', 'wav',
2065
+ 'mp4', 'mov', 'log',
2066
+ ]);
2067
+
2031
2068
  /**
2032
2069
  * Pull field tokens out of a `then` clause. A field token is a word
2033
2070
  * that follows a verb like `set`, `write`, `include`, `return`,
@@ -2052,9 +2089,12 @@ function collectRecordShapeFields(tree) {
2052
2089
  * the check.
2053
2090
  *
2054
2091
  * @param {string} thenClause
2092
+ * @param {Set<string>} [shapeNames] - recordShape names declared in
2093
+ * the tree. Pattern 4 (`<Head>.<field>` dot notation) emits a field
2094
+ * token only when the head is a member of this set (issue 315).
2055
2095
  * @returns {string[]}
2056
2096
  */
2057
- function extractThenFieldTokens(thenClause) {
2097
+ function extractThenFieldTokens(thenClause, shapeNames = new Set()) {
2058
2098
  if (typeof thenClause !== 'string' || thenClause.length === 0) return [];
2059
2099
  const text = stripQuotedSubstrings(thenClause);
2060
2100
  /** @type {Set<string>} */
@@ -2076,11 +2116,33 @@ function extractThenFieldTokens(thenClause) {
2076
2116
  for (const m of text.matchAll(/\bfields?\s*[:=]\s*`?([A-Za-z_][A-Za-z0-9_]*)`?/g)) {
2077
2117
  if (m[1]) tokens.add(m[1]);
2078
2118
  }
2079
- // Pattern 4 (new, issue 311): `<ShapeName>.<fieldName>` dot
2080
- // notation. Shape names are PascalCase; the field name after the
2081
- // dot is the candidate.
2082
- for (const m of text.matchAll(/\b[A-Z][A-Za-z0-9_]*\.([a-z_][A-Za-z0-9_]*)\b/g)) {
2083
- if (m[1]) tokens.add(m[1]);
2119
+ // Pattern 4 (narrowed, issue 315): `<ShapeName>.<fieldName>` dot
2120
+ // notation. Shape names are PascalCase. 0.32.2 emitted a field
2121
+ // token whenever a PascalCase-ish head sat next to a lowercase dot
2122
+ // tail, which read filename stems (`CLAUDE.md`, `*.json`,
2123
+ // `report.json`), non-shape PascalCase heads (`Math.random`,
2124
+ // `ToolExecutionContext.pushLlmCall`), bare-letter version tags
2125
+ // (`corpus-V.json`) and sentence-ending dots inside a dotted
2126
+ // phrase (`Foo.the`) as record fields. Narrowing: emit ONLY when
2127
+ // the head resolves to a recordShape name declared somewhere in
2128
+ // the tree. Three belt-and-braces guards remain regardless of the
2129
+ // shape-name set so a hostile shape-name collision cannot
2130
+ // reintroduce the class: the field is not an English stop word
2131
+ // (sentence-dot), not a known file extension (filename stems),
2132
+ // and the match does not sit inside a path context (preceded by
2133
+ // `/` or `./`, which also catches backticked paths like
2134
+ // `rcf/evals/corpus-V.json`).
2135
+ for (const m of text.matchAll(/\b([A-Z][A-Za-z0-9_]*)\.([a-z_][A-Za-z0-9_]*)\b/g)) {
2136
+ const head = m[1];
2137
+ const field = m[2];
2138
+ if (!head || !field) continue;
2139
+ if (!shapeNames.has(head)) continue;
2140
+ if (STOP.has(field.toLowerCase())) continue;
2141
+ if (PATTERN_4_FILE_EXTENSION_SKIP.has(field.toLowerCase())) continue;
2142
+ const start = m.index ?? 0;
2143
+ const beforeHead = text.slice(Math.max(0, start - 2), start);
2144
+ if (/[\/]/.test(beforeHead)) continue;
2145
+ tokens.add(field);
2084
2146
  }
2085
2147
  // Pattern 1 (narrowed, issue 311): backticked identifier WITH
2086
2148
  // explicit field evidence in its immediate neighbourhood AND no