Compare commits
2 Commits
ff77563133
...
198861f533
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
198861f533 | ||
|
|
7f8cba7f30 |
@@ -137,6 +137,7 @@ Pick a number, or just describe what you need.
|
|||||||
- **The cached `_template.yaml` is your YAML SCAFFOLD** — auto-extracted from the canonical `.drawio` by `tools/archcenter_drawio_to_template.py`. It carries Oracle's container geometry (region/vcn/ad/subnet bboxes), service positions, and edge waypoints in the `absolute_layout:` shape your spec uses. Copy it as the starting point, rename ids, fill in `type:` for services (the extractor leaves `type: TODO_identify` because the canonical icon TYPE is encoded in stencil bytes — pick the right alias from the renderer's TYPE_TO_ICON), and adapt to the customer's components. Lookup result surfaces it as `cached: yaml=<path>`.
|
- **The cached `_template.yaml` is your YAML SCAFFOLD** — auto-extracted from the canonical `.drawio` by `tools/archcenter_drawio_to_template.py`. It carries Oracle's container geometry (region/vcn/ad/subnet bboxes), service positions, and edge waypoints in the `absolute_layout:` shape your spec uses. Copy it as the starting point, rename ids, fill in `type:` for services (the extractor leaves `type: TODO_identify` because the canonical icon TYPE is encoded in stencil bytes — pick the right alias from the renderer's TYPE_TO_ICON), and adapt to the customer's components. Lookup result surfaces it as `cached: yaml=<path>`.
|
||||||
- **The cached `.drawio` is the visual source of truth** if the template extractor missed something — open it (drawio.exe, the SVG companion, or the XML) for the canonical look.
|
- **The cached `.drawio` is the visual source of truth** if the template extractor missed something — open it (drawio.exe, the SVG companion, or the XML) for the canonical look.
|
||||||
- **Do NOT use `examples/` as a geometry source.** `examples/` is previous user output, not authoritative. The `_template.yaml` covers everything `examples/` was being abused for.
|
- **Do NOT use `examples/` as a geometry source.** `examples/` is previous user output, not authoritative. The `_template.yaml` covers everything `examples/` was being abused for.
|
||||||
|
- **Lookup budget: max 2 queries — never loop.** If query 1 returns nothing convincing, run ONE refinement (broader phrasing or different keyword angle), then STOP. Re-querying the catalog 3+ times to find a "better" match is the dominant time-sink in this phase — the catalog has 123 entries indexed once; if two passes can't surface a topology match, none exists. Output of the lookup tool also surfaces a **`⚠ KNOWN GAP`** banner at the top whenever the query touches a documented gap (e.g. OCI-native services accessed FROM GCP via Cross-Cloud Interconnect, or OCI Cache/Redis/PostgreSQL as primary subject). When that banner appears, follow its **Recommended composition** verbatim instead of running more queries — it lists the primitives to combine. When NO gap banner appears AND no top-3 entry matches the topology, present the closest 3 to the user with: *"No exact ref-arch matches. Closest hits are X / Y / Z. I can either base the diagram on the closest of these (geometry transfers, service names don't), or compose from `<list specific slugs / patterns>`. Which?"* — let the user pick the chassis instead of guessing.
|
||||||
2. **Pre-generation review.** Confirm the component list with the user (REQUESTED + TECHNICAL DEPENDENCIES per the whitelist).
|
2. **Pre-generation review.** Confirm the component list with the user (REQUESTED + TECHNICAL DEPENDENCIES per the whitelist).
|
||||||
3. **Author the spec in `absolute_layout` shape** — this is REQUIRED. Do NOT use the legacy workload-driven shape (`tenancy → regions → compartments → services`); it does not resolve OCI icon stencils, does not run through the spec validator, and produces wireframe-looking output (rectangles with text instead of real OCI icons). Every container, service, label, and connection needs explicit `(x, y, w, h)`.
|
3. **Author the spec in `absolute_layout` shape** — this is REQUIRED. Do NOT use the legacy workload-driven shape (`tenancy → regions → compartments → services`); it does not resolve OCI icon stencils, does not run through the spec validator, and produces wireframe-looking output (rectangles with text instead of real OCI icons). Every container, service, label, and connection needs explicit `(x, y, w, h)`.
|
||||||
|
|
||||||
|
|||||||
15
Makefile
15
Makefile
@@ -19,11 +19,20 @@ help: ## Show this help
|
|||||||
|
|
||||||
VENV_PYTHON := $(shell command -v python3.12 2>/dev/null || command -v python3.11 2>/dev/null || command -v python3.10 2>/dev/null || echo python3)
|
VENV_PYTHON := $(shell command -v python3.12 2>/dev/null || command -v python3.11 2>/dev/null || command -v python3.10 2>/dev/null || echo python3)
|
||||||
|
|
||||||
venv: ## Create virtual environment and install dependencies
|
venv: .venv/.deps-installed ## Create or update venv (idempotent — no-op if requirements.txt unchanged)
|
||||||
@echo "Using $(VENV_PYTHON) to create venv..."
|
|
||||||
$(VENV_PYTHON) -m venv .venv
|
# Marker file pattern: the venv only rebuilds when requirements.txt is
|
||||||
|
# newer than the marker. A fresh `make venv` after a successful install
|
||||||
|
# is an instant no-op (was 1m53s on Codex sandboxes — every turn).
|
||||||
|
# To force a rebuild: `rm .venv/.deps-installed` (or `rm -rf .venv`).
|
||||||
|
.venv/.deps-installed: requirements.txt
|
||||||
|
@if [ ! -x .venv/bin/python ]; then \
|
||||||
|
echo "Using $(VENV_PYTHON) to create venv..."; \
|
||||||
|
$(VENV_PYTHON) -m venv .venv; \
|
||||||
|
fi
|
||||||
.venv/bin/pip install --upgrade pip
|
.venv/bin/pip install --upgrade pip
|
||||||
.venv/bin/pip install -r requirements.txt
|
.venv/bin/pip install -r requirements.txt
|
||||||
|
@touch .venv/.deps-installed
|
||||||
@echo "Virtual environment ready. Run: source .venv/bin/activate"
|
@echo "Virtual environment ready. Run: source .venv/bin/activate"
|
||||||
@echo "Tip: run 'make install-hooks' once to enable the pre-commit"
|
@echo "Tip: run 'make install-hooks' once to enable the pre-commit"
|
||||||
@echo " hook that keeps SKILL.md in sync with the Codex copy."
|
@echo " hook that keeps SKILL.md in sync with the Codex copy."
|
||||||
|
|||||||
1
SKILL.md
1
SKILL.md
@@ -135,6 +135,7 @@ Pick a number, or just describe what you need.
|
|||||||
- **The cached `_template.yaml` is your YAML SCAFFOLD** — auto-extracted from the canonical `.drawio` by `tools/archcenter_drawio_to_template.py`. It carries Oracle's container geometry (region/vcn/ad/subnet bboxes), service positions, and edge waypoints in the `absolute_layout:` shape your spec uses. Copy it as the starting point, rename ids, fill in `type:` for services (the extractor leaves `type: TODO_identify` because the canonical icon TYPE is encoded in stencil bytes — pick the right alias from the renderer's TYPE_TO_ICON), and adapt to the customer's components. Lookup result surfaces it as `cached: yaml=<path>`.
|
- **The cached `_template.yaml` is your YAML SCAFFOLD** — auto-extracted from the canonical `.drawio` by `tools/archcenter_drawio_to_template.py`. It carries Oracle's container geometry (region/vcn/ad/subnet bboxes), service positions, and edge waypoints in the `absolute_layout:` shape your spec uses. Copy it as the starting point, rename ids, fill in `type:` for services (the extractor leaves `type: TODO_identify` because the canonical icon TYPE is encoded in stencil bytes — pick the right alias from the renderer's TYPE_TO_ICON), and adapt to the customer's components. Lookup result surfaces it as `cached: yaml=<path>`.
|
||||||
- **The cached `.drawio` is the visual source of truth** if the template extractor missed something — open it (drawio.exe, the SVG companion, or the XML) for the canonical look.
|
- **The cached `.drawio` is the visual source of truth** if the template extractor missed something — open it (drawio.exe, the SVG companion, or the XML) for the canonical look.
|
||||||
- **Do NOT use `examples/` as a geometry source.** `examples/` is previous user output, not authoritative. The `_template.yaml` covers everything `examples/` was being abused for.
|
- **Do NOT use `examples/` as a geometry source.** `examples/` is previous user output, not authoritative. The `_template.yaml` covers everything `examples/` was being abused for.
|
||||||
|
- **Lookup budget: max 2 queries — never loop.** If query 1 returns nothing convincing, run ONE refinement (broader phrasing or different keyword angle), then STOP. Re-querying the catalog 3+ times to find a "better" match is the dominant time-sink in this phase — the catalog has 123 entries indexed once; if two passes can't surface a topology match, none exists. Output of the lookup tool also surfaces a **`⚠ KNOWN GAP`** banner at the top whenever the query touches a documented gap (e.g. OCI-native services accessed FROM GCP via Cross-Cloud Interconnect, or OCI Cache/Redis/PostgreSQL as primary subject). When that banner appears, follow its **Recommended composition** verbatim instead of running more queries — it lists the primitives to combine. When NO gap banner appears AND no top-3 entry matches the topology, present the closest 3 to the user with: *"No exact ref-arch matches. Closest hits are X / Y / Z. I can either base the diagram on the closest of these (geometry transfers, service names don't), or compose from `<list specific slugs / patterns>`. Which?"* — let the user pick the chassis instead of guessing.
|
||||||
2. **Pre-generation review.** Confirm the component list with the user (REQUESTED + TECHNICAL DEPENDENCIES per the whitelist).
|
2. **Pre-generation review.** Confirm the component list with the user (REQUESTED + TECHNICAL DEPENDENCIES per the whitelist).
|
||||||
3. **Author the spec in `absolute_layout` shape** — this is REQUIRED. Do NOT use the legacy workload-driven shape (`tenancy → regions → compartments → services`); it does not resolve OCI icon stencils, does not run through the spec validator, and produces wireframe-looking output (rectangles with text instead of real OCI icons). Every container, service, label, and connection needs explicit `(x, y, w, h)`.
|
3. **Author the spec in `absolute_layout` shape** — this is REQUIRED. Do NOT use the legacy workload-driven shape (`tenancy → regions → compartments → services`); it does not resolve OCI icon stencils, does not run through the spec validator, and produces wireframe-looking output (rectangles with text instead of real OCI icons). Every container, service, label, and connection needs explicit `(x, y, w, h)`.
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,78 @@ last_verified: "2026-03-16"
|
|||||||
source: "https://docs.oracle.com/en/solutions/oracle-architecture-center/"
|
source: "https://docs.oracle.com/en/solutions/oracle-architecture-center/"
|
||||||
entry_count: 123
|
entry_count: 123
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# KNOWN GAPS — topologies the catalog does NOT cover well
|
||||||
|
# =============================================================================
|
||||||
|
#
|
||||||
|
# Surfaced by tools/archcenter_pattern_lookup.py at the top of its output
|
||||||
|
# whenever the user's query tokens match any one of the `triggers` token
|
||||||
|
# sets below. Purpose: stop the lookup loop early when no canonical Oracle
|
||||||
|
# ref-arch exists for what the user asked, and tell the agent how to
|
||||||
|
# COMPOSE the topology from primitives instead of refining query phrasing.
|
||||||
|
#
|
||||||
|
# Schema per gap:
|
||||||
|
# id: unique slug
|
||||||
|
# triggers: list of token sets — fires when the expanded query contains
|
||||||
|
# EVERY token in at least ONE set (OR across sets, AND inside
|
||||||
|
# each set). Tokens are lowercased and synonym-expanded the
|
||||||
|
# same way as scoring tokens.
|
||||||
|
# notice: multi-line string surfaced verbatim above the score-based
|
||||||
|
# top-K. Should explain WHY this is a gap and HOW to compose
|
||||||
|
# from existing primitives. Be concrete (slugs, alias names).
|
||||||
|
#
|
||||||
|
# Add a gap entry only after a real lookup loop wasted user time on a
|
||||||
|
# topology with no canonical match. Speculation belongs elsewhere.
|
||||||
|
|
||||||
|
known_gaps:
|
||||||
|
|
||||||
|
- id: gcp-to-oci-native-services
|
||||||
|
triggers:
|
||||||
|
- [gcp, postgresql]
|
||||||
|
- [gcp, redis]
|
||||||
|
- [gcp, mysql]
|
||||||
|
- [gcp, oke]
|
||||||
|
- [gcp, cache]
|
||||||
|
- [gcp, compute]
|
||||||
|
- [google, postgresql]
|
||||||
|
- [google, redis]
|
||||||
|
- [google, mysql]
|
||||||
|
- [google, oke]
|
||||||
|
- [google, cache]
|
||||||
|
- [google, compute]
|
||||||
|
notice: |
|
||||||
|
No canonical Oracle ref-arch covers OCI-native services (PostgreSQL,
|
||||||
|
OKE, Cache, etc.) accessed FROM Google Cloud via Cross-Cloud
|
||||||
|
Interconnect. The `Database@Google Cloud` entries below are the
|
||||||
|
OPPOSITE direction (Oracle DB running ON GCP, not GCP apps reaching
|
||||||
|
OCI-native services).
|
||||||
|
|
||||||
|
Recommended composition — STOP refining the lookup query, build from:
|
||||||
|
1. A simple VCN baseline (e.g. `start-with-an-oci-virtual-cloud-network`).
|
||||||
|
2. The OCI service in a private subnet behind DRG + FastConnect VC.
|
||||||
|
3. A `GCP <region>` container on the left with `cloud_icon: gcp_compute`
|
||||||
|
(or the relevant GCP service — the renderer maps to mxgraph.gcp2.*
|
||||||
|
the same way it does for AWS).
|
||||||
|
|
||||||
|
- id: newer-oci-services-without-icon
|
||||||
|
triggers:
|
||||||
|
- [redis]
|
||||||
|
- [valkey]
|
||||||
|
- [oci_cache]
|
||||||
|
notice: |
|
||||||
|
OCI Cache with Redis/Valkey is GA after the v24.2 toolkit shipped, so
|
||||||
|
no dedicated ref-arch exists in the catalog and no toolkit icon ships
|
||||||
|
either. Oracle's own multi-service ref archs that DO mention Redis
|
||||||
|
embed inline SVG over the generic database/cache stencil.
|
||||||
|
|
||||||
|
Recommended path — don't keep searching:
|
||||||
|
1. Pick the closest topology BY SHAPE (HA pair, primary+replica,
|
||||||
|
multi-AZ) from the catalog — geometry transfers, names don't.
|
||||||
|
2. Use `type: cache` (or `type: database`) with explicit
|
||||||
|
`label: "OCI Cache (Redis)"`. The renderer's alias table already
|
||||||
|
routes `type: redis` → generic stencil.
|
||||||
|
3. Reference: SKILL.md option 2 step 3 (newer-OCI-services convention).
|
||||||
|
|
||||||
entries:
|
entries:
|
||||||
|
|
||||||
# ===== DATABASE — MULTICLOUD (Database@Azure) =====
|
# ===== DATABASE — MULTICLOUD (Database@Azure) =====
|
||||||
|
|||||||
@@ -327,12 +327,42 @@ def _patterns_for(entry: dict) -> list[str]:
|
|||||||
return _build_patterns_index().get(entry.get("url", ""), [])
|
return _build_patterns_index().get(entry.get("url", ""), [])
|
||||||
|
|
||||||
|
|
||||||
def lookup(query: str, top: int = 5, expand_synonyms: bool = True) -> list[dict]:
|
def _matched_gaps(query_tokens: set[str], gaps: list[dict]) -> list[dict]:
|
||||||
|
"""Return every known gap whose triggers match the (expanded) query.
|
||||||
|
|
||||||
|
A gap fires when ANY one of its `triggers` token sets is a subset of
|
||||||
|
`query_tokens` — i.e. AND inside each set, OR across sets. This
|
||||||
|
matches the "stop searching, compose instead" intent: if even one
|
||||||
|
narrow trigger pair (e.g. `[gcp, postgresql]`) is in the query, the
|
||||||
|
catalog has nothing canonical to offer.
|
||||||
|
"""
|
||||||
|
matched: list[dict] = []
|
||||||
|
for gap in gaps or []:
|
||||||
|
triggers = gap.get("triggers") or []
|
||||||
|
for trigger_set in triggers:
|
||||||
|
tokens_required = {t.lower() for t in trigger_set}
|
||||||
|
if tokens_required and tokens_required.issubset(query_tokens):
|
||||||
|
matched.append(gap)
|
||||||
|
break
|
||||||
|
return matched
|
||||||
|
|
||||||
|
|
||||||
|
def lookup(query: str, top: int = 5, expand_synonyms: bool = True) -> dict:
|
||||||
|
"""Score the catalog against `query` and return the top-K plus any
|
||||||
|
known-gap notices the query triggers.
|
||||||
|
|
||||||
|
Returns a dict shaped as:
|
||||||
|
{"matches": [<top-K entry dicts>], "gaps": [<matched gap dicts>]}
|
||||||
|
|
||||||
|
Callers that only care about matches can read `result["matches"]`.
|
||||||
|
"""
|
||||||
catalog = yaml.safe_load(CATALOG.read_text(encoding="utf-8"))
|
catalog = yaml.safe_load(CATALOG.read_text(encoding="utf-8"))
|
||||||
entries = catalog.get("entries", [])
|
entries = catalog.get("entries", [])
|
||||||
|
gaps_def = catalog.get("known_gaps", []) or []
|
||||||
qt = _tokens(query)
|
qt = _tokens(query)
|
||||||
if expand_synonyms:
|
if expand_synonyms:
|
||||||
qt = _expand_query_tokens(qt)
|
qt = _expand_query_tokens(qt)
|
||||||
|
matched_gaps = _matched_gaps(qt, gaps_def)
|
||||||
scored = []
|
scored = []
|
||||||
# Two-pass scoring: cheap fields first (title/tags/services/summary),
|
# Two-pass scoring: cheap fields first (title/tags/services/summary),
|
||||||
# then enrich the top-K with expensive fields (description text from
|
# then enrich the top-K with expensive fields (description text from
|
||||||
@@ -372,10 +402,19 @@ def lookup(query: str, top: int = 5, expand_synonyms: bool = True) -> list[dict]
|
|||||||
"visual_patterns": _patterns_for(e),
|
"visual_patterns": _patterns_for(e),
|
||||||
"has_description": bool(c.get("_description")),
|
"has_description": bool(c.get("_description")),
|
||||||
})
|
})
|
||||||
return scored
|
return {"matches": scored, "gaps": matched_gaps}
|
||||||
|
|
||||||
|
|
||||||
def _print_results(query: str, results: list[dict]) -> None:
|
def _print_results(query: str, payload: dict) -> None:
|
||||||
|
gaps = payload.get("gaps") or []
|
||||||
|
results = payload.get("matches") or []
|
||||||
|
for gap in gaps:
|
||||||
|
notice = (gap.get("notice") or "").rstrip()
|
||||||
|
gap_id = gap.get("id", "unknown")
|
||||||
|
print(f"⚠ KNOWN GAP — {gap_id}")
|
||||||
|
for line in notice.splitlines():
|
||||||
|
print(f" {line}" if line else "")
|
||||||
|
print()
|
||||||
if not results:
|
if not results:
|
||||||
print(f"No matches for: {query}")
|
print(f"No matches for: {query}")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -966,24 +966,41 @@ class NativePPTXDiagramRenderer:
|
|||||||
return candidates
|
return candidates
|
||||||
|
|
||||||
def _lookup_icon_ref(self, key: str) -> dict | None:
|
def _lookup_icon_ref(self, key: str) -> dict | None:
|
||||||
|
# Stencil buckets sometimes ship a degraded entry — e.g.
|
||||||
|
# database_icons['database'] is tag=sp with bbox=None, which
|
||||||
|
# would clone as a zero-size shape (the "OCI PostgreSQL bbox
|
||||||
|
# but no icon" symptom Diego hit on the GCP↔OCI deck). Skip
|
||||||
|
# any ref that fails the viability check and fall through to
|
||||||
|
# the next source (sample_icon_refs, then shape_library, where
|
||||||
|
# a usable grpSp ref usually exists).
|
||||||
database_icons = (self.index.get("stencils") or {}).get("database_icons") or {}
|
database_icons = (self.index.get("stencils") or {}).get("database_icons") or {}
|
||||||
if key in database_icons:
|
candidate = database_icons.get(key)
|
||||||
return database_icons[key]
|
if candidate and self._is_viable_ref(candidate):
|
||||||
if key in self.sample_icon_refs:
|
return candidate
|
||||||
return self.sample_icon_refs[key]
|
sample = self.sample_icon_refs.get(key)
|
||||||
|
if sample and self._is_viable_ref(sample):
|
||||||
|
return sample
|
||||||
refs = self.shape_library_entries.get(key) or []
|
refs = self.shape_library_entries.get(key) or []
|
||||||
if refs:
|
if refs:
|
||||||
return self._select_preferred_ref(refs)
|
return self._select_preferred_ref(refs)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_viable_ref(ref: dict) -> bool:
|
||||||
|
"""A ref must point at a real cloneable shape (slide path,
|
||||||
|
index/path-into-shape-tree, non-empty bbox, supported tag) for
|
||||||
|
``_clone_translated_block`` to emit visible geometry."""
|
||||||
|
if not ref or not ref.get("slide_path"):
|
||||||
|
return False
|
||||||
|
if ref.get("child_index") is None and not ref.get("node_path"):
|
||||||
|
return False
|
||||||
|
bbox = ref.get("bbox") or {}
|
||||||
|
if not bbox.get("cx") or not bbox.get("cy"):
|
||||||
|
return False
|
||||||
|
return ref.get("tag") in {"grpSp", "pic", "sp"}
|
||||||
|
|
||||||
def _select_preferred_ref(self, refs: list[dict]) -> dict | None:
|
def _select_preferred_ref(self, refs: list[dict]) -> dict | None:
|
||||||
viable = [
|
viable = [ref for ref in refs if self._is_viable_ref(ref)]
|
||||||
ref for ref in refs
|
|
||||||
if (ref.get("child_index") is not None or ref.get("node_path"))
|
|
||||||
and ref.get("slide_path")
|
|
||||||
and (ref.get("bbox") or {}).get("cx")
|
|
||||||
and ref.get("tag") in {"grpSp", "pic", "sp"}
|
|
||||||
]
|
|
||||||
if not viable:
|
if not viable:
|
||||||
return refs[0] if refs else None
|
return refs[0] if refs else None
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user