diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 7abe313..626f48a 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "name": "fluencyloop", "source": ".", "displayName": "FluencyLoop", - "version": "0.3.16", + "version": "0.3.17", "description": "A per-feature loop that designs, teaches, journals decisions, and assembles PR reviews.", "author": { "name": "baokhang83" diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 585771e..0d60366 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "fluencyloop", "displayName": "FluencyLoop", - "version": "0.3.16", + "version": "0.3.17", "description": "Stay fluent in the code your AI agent writes with a per-feature design, build, teaching, and review loop.", "author": { "name": "baokhang83", diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cdded0..bd5b738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to FluencyLoop are documented here. +## 0.3.17 + +### Fixed + +- Feature guidance now normalizes calibration dimensions before probing, so a recorded familiarity + level such as `jacoco-coverage-gates: familiar` is reused rather than asked again. +- Embedded-diagram guidance now requires a visible SVG title and calculated, padded zone bounds; + the reader preflight rejects a missing title or a node that touches a dashed boundary. + ## 0.3.16 ### Fixed diff --git a/claude-skills/diagram-design/SKILL.md b/claude-skills/diagram-design/SKILL.md index c995d58..b1b24df 100644 --- a/claude-skills/diagram-design/SKILL.md +++ b/claude-skills/diagram-design/SKILL.md @@ -76,9 +76,12 @@ exists. ### Embedded iframe contract — no scrollbars The local reader already supplies the figure frame and caption. The embedded document contains only -the diagram: no page title, eyebrow, header, footer, outer `.frame` wrapper, or body padding. Set -`html, body { margin: 0; padding: 0; overflow: hidden; }` and render the SVG with `display: block; -width: 100%; max-width: 100%; height: auto; min-width: 0`. Never add `overflow: auto`, +the diagram: no HTML page title, eyebrow, header, footer, outer `.frame` wrapper, or body padding. +It **must** include a visible, concise title as an SVG `` element near the top. An HTML +`` is metadata, not a visible title. Reserve a dedicated top band of at least 40 SVG units +for it; do not let a zone, connector, or node occupy that band. Set `html, body { margin: 0; +padding: 0; overflow: hidden; }` and render the SVG with `display: block; width: 100%; +max-width: 100%; height: auto; min-width: 0`. Never add `overflow: auto`, `overflow-x: auto`, or a positive SVG `min-width` to an embedded diagram. Before handoff, inspect the iframe at the reader's actual dimensions. Its document must satisfy @@ -98,14 +101,20 @@ iframe width. This applies to native-renderer candidates and fallback HTML alike and 8 units of vertical clearance. Widen or heighten the node, use deliberate two-line text, or choose a shorter faithful label — never let a label escape or clip. - A node inside a dashed or solid region must leave at least 16 SVG units between its outer stroke - and every region edge. Compute the region from its contents plus that padding; do not set a node - edge equal to the region edge or hide the collision with clipping. + and every region edge. Derive each region *after* placing its nodes: use at least 24 SVG units at + its left, right, and bottom, and reserve at least 40 SVG units above its first node for the zone + label and breathing room. Do not set a node edge equal to a region edge or hide a collision with + clipping. If those bounds do not fit, rearrange the nodes or split the visual; never squeeze a + dashed boundary against a node. - Check both light and dark themes. If a native-renderer result fails either check, it does not fit this graph: use the general fallback rather than editing generated HTML or accepting cramped geometry. This is a design loop, not a one-shot fallback: measure the fallback, revise its node sizes, text treatment, region bounds, or layout, then measure again after every revision. Deliver it only after it passes every geometry check. If a clear fallback cannot pass, simplify the visual or keep the explanation in prose or a table; never ship a failed first pass. +- Inspect the completed reader rendering, not only the source. Confirm that the SVG title is + visible and that every dashed boundary has clear space around its enclosed nodes. A missing title + or even one node visually touching a boundary fails the preflight and requires another revision. ## Non-embedded diagrams diff --git a/claude-skills/diagram-design/references/type-architecture.md b/claude-skills/diagram-design/references/type-architecture.md index 6cccfd9..6c21603 100644 --- a/claude-skills/diagram-design/references/type-architecture.md +++ b/claude-skills/diagram-design/references/type-architecture.md @@ -33,11 +33,12 @@ Reserve left/right ports for connections that travel primarily horizontally. Ent **Dashed paths — same routing rules.** Optional, return, async, and passive flows use `stroke-dasharray="4,3"` and a lighter stroke weight (`stroke-width="1"`). Apply the **same orthogonal routing, port-selection, and bridge/hop rules** as solid paths — the dash pattern only communicates semantic weight, not a different routing grammar. When a dashed path and a solid path must cross, bridge the dashed one (it is by definition the less important connection). -**Zone label and containment margin.** Leave ≥16px between the bottom of the zone eyebrow label -and the top of the first enclosed node. Every enclosed node's *outer stroke* must also leave ≥16px -from every zone edge, including the bottom and sides; size the zone from the padded node bounds, -never so a node touches it. A typical top calculation is `zone_y = node_top − 32`, with the label -mask at `zone_y + 4`. +**Zone label and containment margin.** Derive the zone from the placed node rectangles; do not +eyeball the dashed box. Set its left and right edges at least 24px outside the extreme node strokes, +its bottom at least 24px below them, and its top at least 40px above the first node. This gives the +zone label its own band and keeps every node's *outer stroke* clear of every zone edge, including the bottom and sides. +A node must never share or visually touch a dashed boundary. If those padded +bounds do not fit, move or split nodes before drawing the zone. ## Crossing arrows — bridge / hop diff --git a/claude-skills/feature/SKILL.md b/claude-skills/feature/SKILL.md index c196e34..57ddd26 100644 --- a/claude-skills/feature/SKILL.md +++ b/claude-skills/feature/SKILL.md @@ -139,6 +139,14 @@ person-specific knowledge lives (the repo journal stays person-neutral; see Rule entirely is fine — you'll *build* it (see §3.4); `fluencyloop calibration init` seeds it. Never block on it. +**Match calibration dimensions before asking.** Normalize each needed concept and every profile +key to lowercase kebab case: replace every run of non-alphanumeric characters with one `-`, then +trim leading/trailing `-`. Use an exact match on that normalized key. Thus `JaCoCo coverage gates` +matches `jacoco-coverage-gates`. A matched profile value—**including `familiar`**—settles the +familiarity question: apply its teaching depth and do not probe it again. Only a concept with no +normalized profile match is unknown. Do not use loose semantic guessing to manufacture a match, +but never miss an exact normalized match because prose used spaces, case, or punctuation. + **Load the learner's preferences.** Also read `~/.fluencyloop/preferences.md` — a sibling to `calibration.md` (global, per-developer, **never committed**) that records recurring *workflow* choices already settled once, so you never re-ask them — e.g. the completion hand-off (commit + @@ -150,11 +158,13 @@ time a recurring choice comes up. **Probe before you dive in.** Continuously estimating the learner's knowledge is critical, and it starts *before* the first explanation. From the feature's intent and the code, list the domain -concepts this work will actually require, and for each one the knowledge base doesn't already -settle, **ask** — concisely and batched (one tab per concept in Claude Code; one concise, clearly -separated chat prompt in Codex), then wait. For example, before building a Maven plugin: *"Are you familiar with `plugin.xml` and -Mojo objects (`@Mojo` / `AbstractMojo`)?"* — rather than silently guessing and either boring or -losing them. Record the answers into the knowledge base and let them set your opening depth. +concepts this work will actually require, normalize them against the profile as above, and ask only +for the unmatched ones — concisely and batched (one tab per concept in Claude Code; one concise, +clearly separated chat prompt in Codex), then wait. Never ask a familiarity question for a matched +dimension, even when its level is `familiar`; use that level's teaching depth instead. For example, +before building a Maven plugin: *"Are you familiar with `plugin.xml` and Mojo objects (`@Mojo` / +`AbstractMojo`)?"* — rather than silently guessing and either boring or losing them. Record the +answers into the knowledge base and let them set your opening depth. **Probe neutrally; never make explanation sound like a burden.** Do not ask whether you should "keep it light," or imply that the developer needs to opt in to an explanation. Ask what they know diff --git a/plugins/fluencyloop/.codex-plugin/plugin.json b/plugins/fluencyloop/.codex-plugin/plugin.json index 5d5c1df..c1531b1 100644 --- a/plugins/fluencyloop/.codex-plugin/plugin.json +++ b/plugins/fluencyloop/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "fluencyloop", - "version": "0.3.16", + "version": "0.3.17", "description": "Stay fluent in the code your AI agent writes with a per-feature design, build, teaching, and review loop.", "author": { "name": "baokhang83", diff --git a/plugins/fluencyloop/VERSION b/plugins/fluencyloop/VERSION index b88c14d..6e46293 100644 --- a/plugins/fluencyloop/VERSION +++ b/plugins/fluencyloop/VERSION @@ -1 +1 @@ -0.3.16 \ No newline at end of file +0.3.17 \ No newline at end of file diff --git a/plugins/fluencyloop/skills/diagram-design/SKILL.md b/plugins/fluencyloop/skills/diagram-design/SKILL.md index c995d58..b1b24df 100644 --- a/plugins/fluencyloop/skills/diagram-design/SKILL.md +++ b/plugins/fluencyloop/skills/diagram-design/SKILL.md @@ -76,9 +76,12 @@ exists. ### Embedded iframe contract — no scrollbars The local reader already supplies the figure frame and caption. The embedded document contains only -the diagram: no page title, eyebrow, header, footer, outer `.frame` wrapper, or body padding. Set -`html, body { margin: 0; padding: 0; overflow: hidden; }` and render the SVG with `display: block; -width: 100%; max-width: 100%; height: auto; min-width: 0`. Never add `overflow: auto`, +the diagram: no HTML page title, eyebrow, header, footer, outer `.frame` wrapper, or body padding. +It **must** include a visible, concise title as an SVG `<text>` element near the top. An HTML +`<title>` is metadata, not a visible title. Reserve a dedicated top band of at least 40 SVG units +for it; do not let a zone, connector, or node occupy that band. Set `html, body { margin: 0; +padding: 0; overflow: hidden; }` and render the SVG with `display: block; width: 100%; +max-width: 100%; height: auto; min-width: 0`. Never add `overflow: auto`, `overflow-x: auto`, or a positive SVG `min-width` to an embedded diagram. Before handoff, inspect the iframe at the reader's actual dimensions. Its document must satisfy @@ -98,14 +101,20 @@ iframe width. This applies to native-renderer candidates and fallback HTML alike and 8 units of vertical clearance. Widen or heighten the node, use deliberate two-line text, or choose a shorter faithful label — never let a label escape or clip. - A node inside a dashed or solid region must leave at least 16 SVG units between its outer stroke - and every region edge. Compute the region from its contents plus that padding; do not set a node - edge equal to the region edge or hide the collision with clipping. + and every region edge. Derive each region *after* placing its nodes: use at least 24 SVG units at + its left, right, and bottom, and reserve at least 40 SVG units above its first node for the zone + label and breathing room. Do not set a node edge equal to a region edge or hide a collision with + clipping. If those bounds do not fit, rearrange the nodes or split the visual; never squeeze a + dashed boundary against a node. - Check both light and dark themes. If a native-renderer result fails either check, it does not fit this graph: use the general fallback rather than editing generated HTML or accepting cramped geometry. This is a design loop, not a one-shot fallback: measure the fallback, revise its node sizes, text treatment, region bounds, or layout, then measure again after every revision. Deliver it only after it passes every geometry check. If a clear fallback cannot pass, simplify the visual or keep the explanation in prose or a table; never ship a failed first pass. +- Inspect the completed reader rendering, not only the source. Confirm that the SVG title is + visible and that every dashed boundary has clear space around its enclosed nodes. A missing title + or even one node visually touching a boundary fails the preflight and requires another revision. ## Non-embedded diagrams diff --git a/plugins/fluencyloop/skills/diagram-design/references/type-architecture.md b/plugins/fluencyloop/skills/diagram-design/references/type-architecture.md index 6cccfd9..6c21603 100644 --- a/plugins/fluencyloop/skills/diagram-design/references/type-architecture.md +++ b/plugins/fluencyloop/skills/diagram-design/references/type-architecture.md @@ -33,11 +33,12 @@ Reserve left/right ports for connections that travel primarily horizontally. Ent **Dashed paths — same routing rules.** Optional, return, async, and passive flows use `stroke-dasharray="4,3"` and a lighter stroke weight (`stroke-width="1"`). Apply the **same orthogonal routing, port-selection, and bridge/hop rules** as solid paths — the dash pattern only communicates semantic weight, not a different routing grammar. When a dashed path and a solid path must cross, bridge the dashed one (it is by definition the less important connection). -**Zone label and containment margin.** Leave ≥16px between the bottom of the zone eyebrow label -and the top of the first enclosed node. Every enclosed node's *outer stroke* must also leave ≥16px -from every zone edge, including the bottom and sides; size the zone from the padded node bounds, -never so a node touches it. A typical top calculation is `zone_y = node_top − 32`, with the label -mask at `zone_y + 4`. +**Zone label and containment margin.** Derive the zone from the placed node rectangles; do not +eyeball the dashed box. Set its left and right edges at least 24px outside the extreme node strokes, +its bottom at least 24px below them, and its top at least 40px above the first node. This gives the +zone label its own band and keeps every node's *outer stroke* clear of every zone edge, including the bottom and sides. +A node must never share or visually touch a dashed boundary. If those padded +bounds do not fit, move or split nodes before drawing the zone. ## Crossing arrows — bridge / hop diff --git a/plugins/fluencyloop/skills/feature/SKILL.md b/plugins/fluencyloop/skills/feature/SKILL.md index 3fa296e..d78fe7d 100644 --- a/plugins/fluencyloop/skills/feature/SKILL.md +++ b/plugins/fluencyloop/skills/feature/SKILL.md @@ -148,6 +148,14 @@ person-specific knowledge lives (the repo journal stays person-neutral; see Rule entirely is fine — you'll *build* it (see §3.4); `fluencyloop calibration init` seeds it. Never block on it. +**Match calibration dimensions before asking.** Normalize each needed concept and every profile +key to lowercase kebab case: replace every run of non-alphanumeric characters with one `-`, then +trim leading/trailing `-`. Use an exact match on that normalized key. Thus `JaCoCo coverage gates` +matches `jacoco-coverage-gates`. A matched profile value—**including `familiar`**—settles the +familiarity question: apply its teaching depth and do not probe it again. Only a concept with no +normalized profile match is unknown. Do not use loose semantic guessing to manufacture a match, +but never miss an exact normalized match because prose used spaces, case, or punctuation. + **Load the learner's preferences.** Also read `~/.fluencyloop/preferences.md` — a sibling to `calibration.md` (global, per-developer, **never committed**) that records recurring *workflow* choices already settled once, so you never re-ask them — e.g. the completion hand-off (commit + @@ -159,11 +167,13 @@ time a recurring choice comes up. **Probe before you dive in.** Continuously estimating the learner's knowledge is critical, and it starts *before* the first explanation. From the feature's intent and the code, list the domain -concepts this work will actually require, and for each one the knowledge base doesn't already -settle, **ask** — concisely and batched (one tab per concept in Claude Code; one concise, clearly -separated chat prompt in Codex), then wait. For example, before building a Maven plugin: *"Are you familiar with `plugin.xml` and -Mojo objects (`@Mojo` / `AbstractMojo`)?"* — rather than silently guessing and either boring or -losing them. Record the answers into the knowledge base and let them set your opening depth. +concepts this work will actually require, normalize them against the profile as above, and ask only +for the unmatched ones — concisely and batched (one tab per concept in Claude Code; one concise, +clearly separated chat prompt in Codex), then wait. Never ask a familiarity question for a matched +dimension, even when its level is `familiar`; use that level's teaching depth instead. For example, +before building a Maven plugin: *"Are you familiar with `plugin.xml` and Mojo objects (`@Mojo` / +`AbstractMojo`)?"* — rather than silently guessing and either boring or losing them. Record the +answers into the knowledge base and let them set your opening depth. **Probe neutrally; never make explanation sound like a burden.** Do not ask whether you should "keep it light," or imply that the developer needs to opt in to an explanation. Ask what they know diff --git a/tests/plugin.bats b/tests/plugin.bats index ff8e1d9..63dca6a 100644 --- a/tests/plugin.bats +++ b/tests/plugin.bats @@ -185,6 +185,11 @@ for feature_skill_text in [feature_text, codex_feature_text]: assert "**only after the feature is complete**" in feature_skill_text assert "Never distill during a slice, after a decision, or as a turn-by-turn summary" in feature_skill_text assert "fluencyloop calibration show --json" in feature_skill_text + assert "**Match calibration dimensions before asking.**" in feature_skill_text + assert "Thus `JaCoCo coverage gates`\nmatches `jacoco-coverage-gates`." in feature_skill_text + assert "including `familiar`**—settles the\nfamiliarity question" in feature_skill_text + assert "Never ask a familiarity question for a matched\ndimension" in feature_skill_text + assert "Only a concept with no\nnormalized profile match is unknown." in feature_skill_text assert "docs/fluencyloop/distillations/" in feature_skill_text assert "**Feature delta**" in feature_skill_text assert "**no overview rewrite**" in feature_skill_text @@ -238,7 +243,10 @@ for diagram_skill_text in [ assert "confirm it renders with no `Diagram\nunavailable` notice" in diagram_skill_text assert "Do not record or hand off a diagram merely because its file\nexists." in diagram_skill_text assert "### Embedded iframe contract — no scrollbars" in diagram_skill_text - assert "no page title, eyebrow, header, footer, outer `.frame` wrapper" in diagram_skill_text + assert "no HTML page title, eyebrow, header, footer, outer `.frame` wrapper" in diagram_skill_text + assert "visible, concise title as an SVG `<text>` element" in diagram_skill_text + assert "An HTML\n`<title>` is metadata, not a visible title." in diagram_skill_text + assert "dedicated top band of at least 40 SVG units" in diagram_skill_text assert "Never add `overflow: auto`,\n`overflow-x: auto`, or a positive SVG `min-width`" in diagram_skill_text assert "`scrollWidth <= clientWidth` and `scrollHeight <= clientHeight`" in diagram_skill_text assert "within the reader's fixed 33rem height" in diagram_skill_text @@ -249,6 +257,7 @@ for diagram_skill_text in [ assert "This is a design loop, not a one-shot fallback" in diagram_skill_text assert "then measure again after every revision" in diagram_skill_text assert "never ship a failed first pass" in diagram_skill_text + assert "A missing title\n or even one node visually touching a boundary fails the preflight" in diagram_skill_text for full_guide_text in [ read_text(root / "claude-skills" / "diagram-design" / "references" / "full-guide.md"), read_text(dist / "skills" / "diagram-design" / "references" / "full-guide.md"), @@ -262,6 +271,9 @@ for architecture_reference in [ read_text(dist / "skills" / "diagram-design" / "references" / "type-architecture.md"), ]: assert "**Zone label and containment margin.**" in architecture_reference + assert "Derive the zone from the placed node rectangles; do not\neyeball the dashed box." in architecture_reference + assert "at least 24px outside the extreme node strokes" in architecture_reference + assert "top at least 40px above the first node" in architecture_reference assert "including the bottom and sides" in architecture_reference claude_plan_text = read_text(root / "claude-skills" / "plan" / "SKILL.md") codex_plan_text = read_text(dist / "skills" / "plan" / "SKILL.md")