Skip to content
This repository was archived by the owner on Apr 8, 2026. It is now read-only.

Commit f9e4256

Browse files
@fix operator: complete parse-time implementation + linter rules
Grammar: 135 Ohm rules (all lexical for whitespace control) - FixpointExpression (named + anonymous) - Match patterns (path, node, negation, query ref) - Yield productions (relationship, node, node+rel, state, builtin, nested @fix) - Termination conditions (stable, max_iterations, timeout, measure, compound) - Constraint levels (L1 bounded / L2 general) Parser: 40+ semantic actions compiling @fix → fixpoint IR nodes - ext.fix contains structured match/yield/until/constraint data - Status: 'declared' (execution is separate, via FixpointEngine) - Nested @fix creates inner nodes + stores in outer yield Types: fixpoint NodeType, fixpoint_derived RelationType, FixpointExt interface IR Schema: Extended with fixpoint types + 5 graph invariants Linter rules (10 ERROR + 5 WARNING total): - E007: L1 constraint violation (no node creation) - E008: L2 missing explicit bound - E009: Missing constraint level - E010: Unstratifiable negation (WARNING — heuristic) - E011: Nesting de-escalation - W004: High iteration count (>100) Tests: 48 new (779 total), zero regressions 3-layer reviewed: session + PL domain expert + consultation team Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ddf0748 commit f9e4256

13 files changed

Lines changed: 1776 additions & 12 deletions

spec/ir.schema.json

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@
113113
"alternative",
114114
"exploring",
115115
"parking",
116-
"block"
116+
"block",
117+
"fixpoint"
117118
],
118119
"description": "Semantic type of this node"
119120
},
@@ -171,7 +172,8 @@
171172
"different",
172173
"alternative",
173174
"alternative_worse",
174-
"alternative_better"
175+
"alternative_better",
176+
"fixpoint_derived"
175177
],
176178
"description": "Type of relationship between nodes"
177179
},
@@ -312,6 +314,31 @@
312314
"type": "boolean",
313315
"default": true,
314316
"description": "If true, state markers must have required fields. Enforces Decision 3."
317+
},
318+
"fixpoint_constraint_valid": {
319+
"type": "boolean",
320+
"default": true,
321+
"description": "If true, L1 fixpoint nodes have status 'converged' only. L2 fixpoint nodes have status 'converged' or 'bounded'."
322+
},
323+
"fixpoint_convergence_valid": {
324+
"type": "boolean",
325+
"default": true,
326+
"description": "If true, every fixpoint node with status 'converged' has delta_sequence ending in 0."
327+
},
328+
"fixpoint_bound_present": {
329+
"type": "boolean",
330+
"default": true,
331+
"description": "If true, every fixpoint node with constraint 'L2' has an explicit termination bound."
332+
},
333+
"fixpoint_nesting_monotone": {
334+
"type": "boolean",
335+
"default": true,
336+
"description": "If true, nested fixpoint constraint levels are >= outer fixpoint constraint levels."
337+
},
338+
"fixpoint_derivation_complete": {
339+
"type": "boolean",
340+
"default": true,
341+
"description": "If true, every element produced by @fix has a corresponding fixpoint_derived relationship."
315342
}
316343
},
317344
"additionalProperties": false

src/grammar.ohm

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@ FlowScript {
44
Line = BlankLine | RelationshipExpression | Element
55

66
Element = Modifier* Content
7-
Content = State | Insight | Question | Completion | Alternative | Block | Statement
7+
Content = State | Insight | Question | Completion | Alternative | fixpointExpression | Block | Statement
88

99
BlankLine = space* "\n"
10-
Statement = ~Modifier (~("\n" | "{" | "}") any)+ "\n"?
10+
Statement = ~Modifier ~"@fix" (~("\n" | "{" | "}") any)+ "\n"?
1111

1212
// Blocks (thought blocks / atomic processing units)
1313
Block = "{" ws BlockContent? ws "}"
1414
BlockContent = BlockLine (separator? BlockLine)* separator?
1515
BlockLine = ws (ContinuationRel | RelationshipExpression | BlockElement)
1616
BlockElement = Modifier* BlockContent_inner
17-
BlockContent_inner = State | Insight | Question | Completion | Alternative | Block | BlockStatement
18-
BlockStatement = ~Modifier (~("\n" | "{" | "}" | ";") any)+
17+
BlockContent_inner = State | Insight | Question | Completion | Alternative | fixpointExpression | Block | BlockStatement
18+
BlockStatement = ~Modifier ~"@fix" (~("\n" | "{" | "}" | ";") any)+
1919
separator = (space | "\n" | ";")+
2020
ws = (space | "\n")*
2121

@@ -127,6 +127,107 @@ FlowScript {
127127

128128
identifier = letter (letter | digit | "_" | "-")*
129129

130+
// =========================================================================
131+
// Fixpoint Expressions (@fix operator)
132+
// All @fix rules are LEXICAL (lowercase) for explicit whitespace control.
133+
// Syntactic rules auto-insert spaces which conflicts with @fix's
134+
// multi-line block structure. Lexical rules give full control.
135+
// =========================================================================
136+
137+
// @fix name? { clauses }
138+
fixpointExpression = fixpointExpression_named | fixpointExpression_anon
139+
fixpointExpression_named = "@fix" space+ fixName ws "{" ws fixpointClauses ws "}"
140+
fixpointExpression_anon = "@fix" ws "{" ws fixpointClauses ws "}"
141+
142+
fixName = letter (letter | digit | "_")*
143+
144+
fixpointClauses = fixpointClause (ws fixpointClause)*
145+
fixpointClause = fixMatchClause | fixYieldClause | fixUntilClause | fixConstraintClause
146+
147+
fixMatchClause = "match" ws ":" ws fixMatchBody
148+
fixYieldClause = "yield" ws ":" ws fixYieldBody
149+
fixUntilClause = "until" ws ":" ws fixTerminationCondition
150+
fixConstraintClause = "constraint" ws ":" ws constraintLevel
151+
152+
constraintLevel = "L2" | "L1"
153+
154+
// --- Match patterns ---
155+
fixMatchBody = fixMatchBody_braced | fixMatchBody_query
156+
fixMatchBody_braced = "{" ws fixPatternList ws "}"
157+
fixMatchBody_query = fixQueryRef
158+
fixPatternList = fixPatternElement (ws "," ws fixPatternElement)*
159+
fixPatternElement = fixNegationPattern | fixPathPattern | fixNodePattern
160+
161+
// Path pattern: A: node -> trusts -> B: node
162+
fixPathPattern = fixNodePattern (ws "->" ws fixEdgeLabel ws "->" ws fixNodePattern)+
163+
fixEdgeLabel = letter (letter | digit | "_")*
164+
165+
// Node pattern: A: node or A: thought(predicate1, predicate2)
166+
fixNodePattern = fixVariable ws ":" ws fixNodeType fixMatchCondition?
167+
fixMatchCondition = "(" ws fixPredicateList ws ")"
168+
fixPredicateList = fixPredicate (ws "," ws fixPredicate)*
169+
fixPredicate = fixPredicateName "(" ws fixArgList? ws ")"
170+
fixPredicateName = letter (letter | digit | "_")*
171+
172+
// Negation: not node_pattern | not query_ref
173+
fixNegationPattern = fixNegationPattern_node | fixNegationPattern_query
174+
fixNegationPattern_node = "not" space+ fixNodePattern
175+
fixNegationPattern_query = "not" space+ fixQueryRef
176+
177+
// Query references: tensions(), blocked(), etc.
178+
fixQueryRef = fixQueryName "(" ws fixArgList? ws ")"
179+
fixQueryName = "tensions" | "blocked" | "alternatives" | "why" | "what_if"
180+
fixArgList = fixArg (ws "," ws fixArg)*
181+
fixArg = fixVariable | string
182+
183+
// --- Yield productions ---
184+
fixYieldBody = fixYieldBody_braced | fixYieldBody_nested | fixYieldBody_builtin
185+
fixYieldBody_braced = "{" ws fixYieldList ws "}"
186+
fixYieldBody_nested = fixpointExpression
187+
fixYieldBody_builtin = fixBuiltinAction
188+
fixYieldList = fixYieldElement (ws "," ws fixYieldElement)*
189+
fixYieldElement = fixNodeRelProduction | fixNodeProduction | fixBuiltinAction | fixRelProduction | fixStateProduction | fixpointExpression
190+
191+
// new hypothesis(X) -> resolves -> X | source: abductive (combined create + connect)
192+
fixNodeRelProduction = "new" space+ fixNodeKind "(" ws fixArgList? ws ")" ws "->" ws fixEdgeLabel ws "->" ws fixVariable fixAnnotation*
193+
194+
// new hypothesis(X) | source: abductive (create only)
195+
fixNodeProduction = "new" space+ fixNodeKind "(" ws fixArgList? ws ")" fixAnnotation*
196+
fixNodeKind = letter (letter | digit | "_")*
197+
198+
// A -> believes -> P | confidence: derived
199+
fixRelProduction = fixVariable ws "->" ws fixEdgeLabel ws "->" ws fixVariable fixAnnotation*
200+
201+
// resolve(X) | annotate(X)
202+
fixStateProduction = fixStateAction "(" ws fixVariable ws ")" fixAnnotation*
203+
fixStateAction = "resolve" | "annotate"
204+
205+
// resolve(matched) — builtin action on query results
206+
fixBuiltinAction = "resolve" "(" ws "matched" ws ")" fixAnnotation*
207+
208+
// Annotations: | key: value
209+
fixAnnotation = ws "|" ws fixAnnotationKey ws ":" ws fixAnnotationValue
210+
fixAnnotationKey = letter (letter | digit | "_")*
211+
fixAnnotationValue = string | fixInteger | fixAnnotationIdent
212+
fixAnnotationIdent = letter (letter | digit | "_")*
213+
214+
// --- Termination conditions ---
215+
fixTerminationCondition = fixSimpleTermCondition (ws "or" ws fixSimpleTermCondition)*
216+
fixSimpleTermCondition = fixIterationBound | fixTimeoutBound | fixMeasureBound | fixStableCondition
217+
fixStableCondition = "stable"
218+
fixIterationBound = "max_iterations" ws ":" ws fixInteger
219+
fixTimeoutBound = "timeout" ws ":" ws fixInteger fixTimeUnit
220+
fixTimeUnit = "ms" | "s" | "m"
221+
fixMeasureBound = "measure" ws ":" ws fixMeasureName
222+
fixMeasureName = letter (letter | digit | "_")*
223+
224+
// Fixpoint lexical primitives
225+
fixVariable = upper (letter | digit | "_")*
226+
fixInteger = digit+
227+
fixNodeType = "fixpoint" | "thought" | "question" | "decision" | "blocker" | "action"
228+
| "statement" | "insight" | "completion" | "alternative"
229+
| "exploring" | "parking" | "block" | "node"
230+
130231
// Lexical rules (whitespace handling)
131232
space := " " | "\t" | "\r"
132233
}

src/linter.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,23 +75,35 @@ export class Linter {
7575
OrphanedNodesRule,
7676
CausalCyclesRule,
7777
AlternativesWithoutDecisionRule,
78+
FixpointConstraintViolationRule,
79+
FixpointL2MissingBoundRule,
80+
FixpointMissingConstraintRule,
81+
FixpointUnstratifiableNegationRule,
82+
FixpointNestingDeEscalationRule,
7883
MissingRecommendedFieldsRule,
7984
DeepNestingRule,
80-
LongCausalChainsRule
85+
LongCausalChainsRule,
86+
FixpointHighIterationCountRule
8187
} = require('./rules');
8288

83-
// ERROR rules (6 total)
89+
// ERROR rules (10 total)
8490
this.rules.push(new UnlabeledTensionRule());
8591
this.rules.push(new MissingRequiredFieldsRule());
8692
this.rules.push(new InvalidSyntaxRule());
8793
this.rules.push(new OrphanedNodesRule());
8894
this.rules.push(new CausalCyclesRule());
8995
this.rules.push(new AlternativesWithoutDecisionRule());
96+
this.rules.push(new FixpointConstraintViolationRule());
97+
this.rules.push(new FixpointL2MissingBoundRule());
98+
this.rules.push(new FixpointMissingConstraintRule());
99+
this.rules.push(new FixpointNestingDeEscalationRule());
90100

91-
// WARNING rules (3 total)
101+
// WARNING rules (5 total)
92102
this.rules.push(new MissingRecommendedFieldsRule());
93103
this.rules.push(new DeepNestingRule());
94104
this.rules.push(new LongCausalChainsRule());
105+
this.rules.push(new FixpointUnstratifiableNegationRule());
106+
this.rules.push(new FixpointHighIterationCountRule());
95107
}
96108

97109
/**

0 commit comments

Comments
 (0)