Merge branch 'master' into master

pull/1764/head
sjbrenchley89 2 weeks ago committed by GitHub
commit e1ee70fa20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,498 @@
const repository = process.env.GITHUB_REPOSITORY || "CCOSTAN/Home-AssistantConfig";
const [owner, repo] = repository.split("/");
const serverUrl = process.env.GITHUB_SERVER_URL || "https://github.com";
const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com";
const token = process.env.GITHUB_TOKEN || "";
const dryRun = process.env.DIGEST_DRY_RUN === "1" || process.argv.includes("--dry-run");
if (!owner || !repo) {
throw new Error(`GITHUB_REPOSITORY must be owner/repo, got "${repository}"`);
}
if (!token && !dryRun) {
throw new Error("GITHUB_TOKEN is required unless DIGEST_DRY_RUN=1 is set.");
}
const now = process.env.DIGEST_NOW ? new Date(process.env.DIGEST_NOW) : new Date();
const end = process.env.DIGEST_END ? new Date(process.env.DIGEST_END) : now;
const start = process.env.DIGEST_START
? new Date(process.env.DIGEST_START)
: new Date(end.getTime() - 7 * 24 * 60 * 60 * 1000);
for (const [name, date] of [["DIGEST_START", start], ["DIGEST_END", end]]) {
if (Number.isNaN(date.getTime())) {
throw new Error(`${name} is not a valid date.`);
}
}
const defaultHeaders = {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "home-assistant-config-weekly-digest",
};
if (token) {
defaultHeaders.Authorization = `Bearer ${token}`;
}
function formatDigestDate(date) {
const day = new Intl.DateTimeFormat("en-US", { day: "numeric", timeZone: "UTC" }).format(date);
const month = new Intl.DateTimeFormat("en-US", { month: "long", timeZone: "UTC" }).format(date);
const year = new Intl.DateTimeFormat("en-US", { year: "numeric", timeZone: "UTC" }).format(date);
return `${day} ${month}, ${year}`;
}
function formatCountVerb(count, singular, plural) {
return count === 1 ? singular : plural;
}
function searchRange(field) {
return `${field}:${start.toISOString()}..${end.toISOString()}`;
}
function isWithinWindow(value) {
if (!value) {
return false;
}
const date = new Date(value);
return !Number.isNaN(date.getTime()) && date >= start && date <= end;
}
function markdownLink(label, url) {
const safeLabel = String(label || "unknown").replace(/[[\]]/g, "\\$&");
return `[${safeLabel}](${url})`;
}
function userLink(user) {
if (!user || !user.login) {
return "unknown";
}
return markdownLink(user.login, user.html_url || `${serverUrl}/${user.login}`);
}
function issueLine(item, icon) {
return `${icon} #${item.number} ${markdownLink(item.title, item.html_url)}, by ${userLink(item.user)}`;
}
function commitLine(commit) {
const title = commit.commit?.message?.split("\n")[0] || commit.sha;
const author = commit.author
? userLink(commit.author)
: commit.commit?.author?.name || "unknown";
return `:hammer_and_wrench: ${markdownLink(title, commit.html_url)} by ${author}`;
}
function releaseLine(release) {
const label = release.name || release.tag_name;
return `:bookmark: ${markdownLink(label, release.html_url)}`;
}
function stargazerLine(star) {
return `:star: ${userLink(star.user)}`;
}
function sectionDivider(lines) {
lines.push("");
lines.push(" - - - ");
}
async function githubRequest(method, path, options = {}) {
const query = options.query || {};
const url = new URL(`${apiUrl}${path}`);
for (const [key, value] of Object.entries(query)) {
if (value !== undefined && value !== null) {
url.searchParams.set(key, String(value));
}
}
const response = await fetch(url, {
method,
headers: {
...defaultHeaders,
...(options.headers || {}),
},
body: options.body ? JSON.stringify(options.body) : undefined,
});
const text = await response.text();
const data = text ? JSON.parse(text) : null;
if (!response.ok) {
const message = data?.message || text || response.statusText;
throw new Error(`${method} ${url.pathname} failed: ${response.status} ${message}`);
}
return { data, headers: response.headers };
}
async function getAllPages(path, options = {}) {
const results = [];
let page = 1;
while (true) {
const { data } = await githubRequest("GET", path, {
...options,
query: {
...(options.query || {}),
per_page: 100,
page,
},
});
if (!Array.isArray(data) || data.length === 0) {
break;
}
results.push(...data);
if (data.length < 100) {
break;
}
page += 1;
}
return results;
}
async function searchIssues(query) {
const results = [];
let page = 1;
while (true) {
const { data } = await githubRequest("GET", "/search/issues", {
query: {
q: query,
sort: "created",
order: "asc",
per_page: 100,
page,
},
});
const items = data.items || [];
results.push(...items);
if (items.length < 100 || results.length >= data.total_count) {
break;
}
page += 1;
}
return results;
}
function hasLabel(item, labelName) {
return (item.labels || []).some((label) => label.name === labelName);
}
async function getCreatedIssueItems() {
const items = await searchIssues(`repo:${owner}/${repo} ${searchRange("created")}`);
return items.filter((item) => !hasLabel(item, "weekly-digest"));
}
async function getPullRequests() {
const updated = await searchIssues(`repo:${owner}/${repo} is:pr ${searchRange("updated")}`);
const merged = await searchIssues(`repo:${owner}/${repo} is:pr ${searchRange("merged")}`);
const byNumber = new Map();
for (const item of [...updated, ...merged]) {
byNumber.set(item.number, item);
}
const pulls = [];
for (const item of byNumber.values()) {
const { data } = await githubRequest("GET", `/repos/${owner}/${repo}/pulls/${item.number}`);
pulls.push({
...item,
merged_at: data.merged_at,
});
}
return pulls.sort((a, b) => a.number - b.number);
}
async function getCommits() {
const commits = await getAllPages(`/repos/${owner}/${repo}/commits`, {
query: {
since: start.toISOString(),
until: end.toISOString(),
},
});
return commits.sort((a, b) => new Date(a.commit.author.date) - new Date(b.commit.author.date));
}
async function getReleases() {
const releases = await getAllPages(`/repos/${owner}/${repo}/releases`);
return releases
.filter((release) => isWithinWindow(release.published_at || release.created_at))
.sort((a, b) => new Date(a.published_at || a.created_at) - new Date(b.published_at || b.created_at));
}
async function getStargazers() {
const stars = await getAllPages(`/repos/${owner}/${repo}/stargazers`, {
headers: {
Accept: "application/vnd.github.star+json",
},
});
return stars
.filter((star) => isWithinWindow(star.starred_at))
.sort((a, b) => new Date(a.starred_at) - new Date(b.starred_at));
}
function getReactionTotal(item) {
const reactions = item.reactions || {};
return (reactions["+1"] || 0) + (reactions.smile || 0) + (reactions.tada || 0) + (reactions.heart || 0);
}
function appendIssues(lines, createdItems) {
const openItems = createdItems.filter((item) => item.state === "open");
const closedItems = createdItems.filter((item) => item.state === "closed");
const likedItem = [...createdItems].sort((a, b) => getReactionTotal(b) - getReactionTotal(a))[0];
const noisyItem = [...createdItems].sort((a, b) => (b.comments || 0) - (a.comments || 0))[0];
lines.push("# ISSUES");
lines.push(`Last week ${createdItems.length} issues were created.`);
lines.push(`Of these, ${closedItems.length} issues have been closed and ${openItems.length} issues are still open.`);
if (openItems.length > 0) {
lines.push("## OPEN ISSUES");
lines.push(...openItems.map((item) => issueLine(item, ":green_heart:")));
}
if (closedItems.length > 0) {
lines.push("## CLOSED ISSUES");
lines.push(...closedItems.map((item) => issueLine(item, ":heart:")));
}
if (likedItem && getReactionTotal(likedItem) > 0) {
const reactions = likedItem.reactions || {};
lines.push("## LIKED ISSUE");
lines.push(issueLine(likedItem, ":+1:"));
lines.push(
`It received :+1: x${reactions["+1"] || 0}, :smile: x${reactions.smile || 0}, :tada: x${
reactions.tada || 0
} and :heart: x${reactions.heart || 0}.`,
);
}
if (noisyItem && noisyItem.comments > 0) {
lines.push("## NOISY ISSUE");
lines.push(issueLine(noisyItem, ":speaker:"));
lines.push(`It received ${noisyItem.comments} comments.`);
}
}
function appendPullRequests(lines, pulls) {
const openPulls = pulls.filter((pull) => pull.state === "open");
const mergedPulls = pulls.filter((pull) => isWithinWindow(pull.merged_at));
const closedPulls = pulls.filter((pull) => pull.state === "closed" && !isWithinWindow(pull.merged_at));
lines.push("# PULL REQUESTS");
if (pulls.length === 0) {
lines.push("Last week, no pull requests were created, updated or merged.");
return;
}
lines.push(`Last week, ${pulls.length} pull requests were created, updated or merged.`);
if (openPulls.length > 0) {
lines.push(`## OPEN PULL ${formatCountVerb(openPulls.length, "REQUEST", "REQUESTS")}`);
lines.push(...openPulls.map((item) => issueLine(item, ":green_heart:")));
}
if (closedPulls.length > 0) {
lines.push(`## CLOSED PULL ${formatCountVerb(closedPulls.length, "REQUEST", "REQUESTS")}`);
lines.push(...closedPulls.map((item) => issueLine(item, ":heart:")));
}
if (mergedPulls.length > 0) {
lines.push(`## MERGED PULL ${formatCountVerb(mergedPulls.length, "REQUEST", "REQUESTS")}`);
lines.push(...mergedPulls.map((item) => issueLine(item, ":purple_heart:")));
}
}
function appendCommits(lines, commits) {
lines.push("# COMMITS");
if (commits.length === 0) {
lines.push("Last week there were no commits.");
return;
}
lines.push(`Last week there were ${commits.length} commits.`);
lines.push(...commits.map(commitLine));
}
function appendContributors(lines, commits, createdItems, pulls) {
const contributors = new Map();
for (const commit of commits) {
if (commit.author?.login) {
contributors.set(commit.author.login, commit.author);
}
}
if (contributors.size === 0) {
for (const item of [...createdItems, ...pulls]) {
if (item.user?.login) {
contributors.set(item.user.login, item.user);
}
}
}
lines.push("# CONTRIBUTORS");
lines.push(`Last week there ${formatCountVerb(contributors.size, "was", "were")} ${contributors.size} contributors.`);
lines.push(...[...contributors.values()].map((user) => `:bust_in_silhouette: ${userLink(user)}`));
}
function appendStargazers(lines, stargazers) {
lines.push("# STARGAZERS");
if (stargazers.length === 0) {
lines.push("Last week there were no stagazers.");
return;
}
lines.push(`Last week there were ${stargazers.length} stagazers.`);
lines.push(...stargazers.slice(0, 75).map(stargazerLine));
if (stargazers.length > 75) {
lines.push(`...and ${stargazers.length - 75} more.`);
}
lines.push("You all are the stars! :star2:");
}
function appendReleases(lines, releases) {
lines.push("# RELEASES");
if (releases.length === 0) {
lines.push("Last week there were no releases.");
return;
}
lines.push(`Last week there ${formatCountVerb(releases.length, "was", "were")} ${releases.length} releases.`);
lines.push(...releases.map(releaseLine));
}
function buildDigestBody({ createdItems, pulls, commits, stargazers, releases }) {
const repoUrl = `${serverUrl}/${owner}/${repo}`;
const lines = [
`Here's the **Weekly Digest** for [*${owner}/${repo}*](${repoUrl}):`,
];
sectionDivider(lines);
appendIssues(lines, createdItems);
sectionDivider(lines);
appendPullRequests(lines, pulls);
sectionDivider(lines);
appendCommits(lines, commits);
sectionDivider(lines);
appendContributors(lines, commits, createdItems, pulls);
sectionDivider(lines);
appendStargazers(lines, stargazers);
sectionDivider(lines);
appendReleases(lines, releases);
sectionDivider(lines);
lines.push("");
lines.push(
`That's all for last week, please :eyes: **Watch** and :star: **Star** the repository [*${owner}/${repo}*](${repoUrl}) to receive next weekly updates. :smiley:`,
);
lines.push("");
lines.push(
`*You can also [view all Weekly Digests by clicking here](${repoUrl}/issues?q=is:open+is:issue+label:weekly-digest).* `,
);
lines.push("");
lines.push(`> Your [**Weekly Digest**](${repoUrl}/actions/workflows/weekly-digest.yml) bot. :calendar:`);
return lines.join("\n");
}
async function getOpenDigestIssues() {
return getAllPages(`/repos/${owner}/${repo}/issues`, {
query: {
state: "open",
labels: "weekly-digest",
},
});
}
async function createDigestIssue(title, body) {
const { data } = await githubRequest("POST", `/repos/${owner}/${repo}/issues`, {
body: {
title,
body,
labels: ["weekly-digest"],
},
});
return data;
}
async function closeOldDigestIssue(issue, newIssue) {
const labels = new Set((issue.labels || []).map((label) => label.name));
labels.add("oldnews");
await githubRequest("POST", `/repos/${owner}/${repo}/issues/${issue.number}/comments`, {
body: {
body: `A new weekly digest is available: #${newIssue.number}. Closing this older digest so only the latest one stays open.`,
},
});
await githubRequest("PATCH", `/repos/${owner}/${repo}/issues/${issue.number}`, {
body: {
state: "closed",
state_reason: "completed",
},
});
try {
await githubRequest("PATCH", `/repos/${owner}/${repo}/issues/${issue.number}`, {
body: {
labels: [...labels],
},
});
} catch (error) {
console.warn(`Could not add oldnews label to #${issue.number}: ${error.message}`);
}
}
async function main() {
const title = `Weekly Digest (${formatDigestDate(start)} - ${formatDigestDate(end)})`;
const openDigestIssues = await getOpenDigestIssues();
const [createdItems, pulls, commits, stargazers, releases] = await Promise.all([
getCreatedIssueItems(),
getPullRequests(),
getCommits(),
getStargazers(),
getReleases(),
]);
const body = buildDigestBody({ createdItems, pulls, commits, stargazers, releases });
if (dryRun) {
console.log(`DRY RUN: would create "${title}"`);
console.log(body);
console.log(`DRY RUN: would close ${openDigestIssues.length} older digest issues.`);
return;
}
const newIssue = await createDigestIssue(title, body);
const oldIssues = openDigestIssues.filter((issue) => issue.number !== newIssue.number);
for (const issue of oldIssues) {
await closeOldDigestIssue(issue, newIssue);
}
console.log(`Created ${newIssue.html_url}`);
console.log(`Closed ${oldIssues.length} older digest issues.`);
}
await main();

@ -0,0 +1,31 @@
name: Weekly Digest
on:
schedule:
- cron: "37 6 * * 1"
workflow_dispatch:
permissions:
contents: read
issues: write
pull-requests: read
jobs:
weekly-digest:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Create weekly digest issue
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_API_URL: ${{ github.api_url }}
run: node .github/scripts/weekly-digest.mjs

@ -18,6 +18,11 @@
Live, personal Home Assistant configuration shared for **browsing and inspiration**. This is not a turnkey clone-and-run setup; borrow ideas, adapt entity IDs/secrets, and test in your own environment.
### Latest video: Clean Home Assistant dashboards
[![Build Clean Home Assistant Dashboards with YAML Partials and Templates](https://img.youtube.com/vi/aFis2YPeSuY/maxresdefault.jpg)](https://youtu.be/aFis2YPeSuY)
This walkthrough shows how the Infrastructure dashboard is organized with a boring `dashboard.yaml`, thin view files, focused partials, and shared button-card templates. [Watch the video](https://youtu.be/aFis2YPeSuY), read the [companion post](https://www.vcloudinfo.com/2026/02/home-assistant-dashboard-design-system-button-card.html), and browse the YAML under [config/dashboards/infrastructure](config/dashboards/infrastructure).
### Quick navigation
- You are here: `/` (root repo guide)
- [Blog](https://www.vcloudinfo.com) | [Issues](https://github.com/CCOSTAN/Home-AssistantConfig/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) | [Diagram](config/www/custom_ui/floorplan/images/branding/Bear-Stone-Docker-Diagram.jpg) | [YouTube](https://youtube.com/vCloudInfo)

@ -18,6 +18,10 @@
Codex skills stored in-repo so they can be shared with the community. These are documentation + helper scripts only (no secrets).
### Walkthroughs
- Dashboard design skill: [Build Clean Home Assistant Dashboards with YAML Partials and Templates](https://youtu.be/aFis2YPeSuY)
- Companion post: [Home Assistant Dashboard Design System (Button-Card First)](https://www.vcloudinfo.com/2026/02/home-assistant-dashboard-design-system-button-card.html)
### Quick navigation
- You are here: `codex_skills/`
- [Repo overview](../README.md) | [Dashboards](../config/dashboards/README.md) | [Issues](https://github.com/CCOSTAN/Home-AssistantConfig/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)
@ -26,7 +30,7 @@ Codex skills stored in-repo so they can be shared with the community. These are
- `homeassistant-dashboard-designer/`: Constrained, button-card-first Lovelace dashboard design system + YAML lint helper.
- `homeassistant-yaml-dry-verifier/`: Home Assistant YAML DRY verifier to detect redundant triggers/conditions/actions/sequence blocks and suggest refactors.
- `infrastructure-doc-sync/`: Session closeout workflow to update AGENTS/README/Dashy shortcuts/Infra Info snapshot consistently after infra changes.
- `infrastructure-doc-sync/`: Session closeout workflow to update AGENTS/README/Dashy shortcuts/BearClaw infrastructure snapshot consistently after infra changes.
- `network-architecture-diagrammer/`: Mermaid-first homelab/network architecture diagram workflow for Excalidraw-friendly topology and service maps.
### Notes

@ -20,6 +20,10 @@
This directory contains the `homeassistant-dashboard-designer` Codex skill, stored in-repo so it can be shared with the community.
### Walkthrough
- Video: [Build Clean Home Assistant Dashboards with YAML Partials and Templates](https://youtu.be/aFis2YPeSuY)
- Companion post: [Home Assistant Dashboard Design System (Button-Card First)](https://www.vcloudinfo.com/2026/02/home-assistant-dashboard-design-system-button-card.html)
### Quick navigation
- You are here: `codex_skills/homeassistant-dashboard-designer/`
- [Repo overview](../../README.md) | [Codex skills](../README.md) | [Dashboards](../../config/dashboards/README.md) | [Issues](https://github.com/CCOSTAN/Home-AssistantConfig/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)

@ -31,6 +31,7 @@ This directory contains the `homeassistant-yaml-dry-verifier` skill and the CLI
- Detects duplicate entries within a single block (`INTRA`).
- Detects package-defined scripts called from multiple files (`CENTRAL_SCRIPT`).
- Collapses noisy ENTRY reports when they are already fully explained by an identical `FULL_BLOCK` finding.
- Adds workflow guardrails for automation refactors that rename/remove entity references or introduce cleanup behavior: stale-reference checks, dry-run/preview expectations, explicit confirmation, and audit/backup output.
## CLI Usage
@ -70,6 +71,8 @@ Exit codes:
- This verifier intentionally keeps text output and a small CLI surface.
- It does not implement suppression files, severity scoring, JSON output, or diff-only mode.
- It is not an orphan entity cleaner and should not delete Home Assistant registry entries during normal DRY runs.
- Treat generic `unavailable`, disabled, or no-`config_entry_id` entities as audit signals only; YAML, helper, REST, command-line, MQTT, finance, YouTube, and local infrastructure telemetry can be intentional.
- Use it as a fast pre-refactor signal and pair with Home Assistant config validation before restart/reload.
**All of my configuration files are tested against the most stable version of home-assistant.**

@ -1,6 +1,6 @@
---
name: homeassistant-yaml-dry-verifier
description: "Verify Home Assistant YAML for DRY and efficiency issues by detecting redundant trigger/condition/action/sequence structures and repeated blocks across automations, scripts, and packages. Use when creating, reviewing, or refactoring YAML in config/packages, config/automations, config/scripts, or dashboard-related YAML where duplication risk is high."
description: "Verify Home Assistant YAML for DRY and efficiency issues by detecting redundant trigger/condition/action/sequence structures and repeated blocks across automations, scripts, and packages. Use when creating, reviewing, or refactoring YAML in config/packages, config/automations, config/scripts, or dashboard-related YAML where duplication risk is high. Include a read-only entity/reference safety pass when automation changes rename/remove entities or introduce maintenance cleanup behavior."
---
# Home Assistant YAML DRY Verifier
@ -13,13 +13,15 @@ Use this skill to lint Home Assistant YAML for repeat logic before or after edit
- Resolve the findings in the same task by refactoring YAML to remove duplication.
- Re-run the verifier after refactoring and iterate until targeted findings are cleared.
- If a finding cannot be safely resolved, explicitly document the blocker and the smallest safe follow-up.
- If touched YAML performs cleanup, registry hygiene, purge, deletion, or other destructive maintenance, require preview/dry-run behavior, explicit confirmation, and audit/backup output before any destructive action is considered complete.
## Quick Start
1. Run the verifier script on the file(s) you edited.
2. Review repeated block findings first (highest confidence).
3. Refactor into shared scripts/helpers/templates where appropriate.
4. Re-run the verifier and then run your normal Home Assistant config check.
4. If the change renames/removes entity references or adds maintenance cleanup behavior, perform the read-only entity/reference safety pass.
5. Re-run the verifier and then run your normal Home Assistant config check.
```bash
python codex_skills/homeassistant-yaml-dry-verifier/scripts/verify_ha_yaml_dry.py config/packages/life360.yaml --strict
@ -53,15 +55,25 @@ python codex_skills/homeassistant-yaml-dry-verifier/scripts/verify_ha_yaml_dry.p
- Repeated triggers: consolidate where behavior is equivalent, or split by intent if readability improves.
- For cooldown/throttle behavior, prefer automation-local `this.attributes.last_triggered` with custom event handoff before adding new helper entities, unless shared persistent state is required across automations.
5. Validate after edits:
5. Entity/Registry Safety Pass:
- Keep this pass read-only during normal DRY work. Report possible stale references or risky cleanup behavior; do not delete Home Assistant registry entries as part of this skill.
- When refactors rename, remove, or consolidate automations, scripts, helpers, entities, service calls, or dashboard targets, search touched and adjacent YAML for stale references before closing the task.
- Use live Home Assistant context or registry/state evidence when available and in scope, especially before changing entity IDs or automations that depend on device/integration state.
- Do not treat generic `unavailable`, disabled, or no-`config_entry_id` entities as safe deletion candidates. In YAML-heavy setups these are often intentional.
- Treat these platforms as common false-positive sources unless stronger evidence proves otherwise: `automation`, `script`, `scene`, `template`, helpers (`input_*`, `group`, `timer`, `counter`, `schedule`, `zone`, `person`, `tag`), `command_line`, `rest`, `mqtt`, `yahoofinance`, `youtube`, and local infrastructure telemetry.
- For cleanup or maintenance automations, prefer a preview/report action first, persistent ignore rules where repeated noise is expected, and an audit artifact that records what would change or did change.
- Destructive cleanup must be gated by explicit operator confirmation and should have backup/audit output. A dry-run-only recommendation is acceptable when the evidence is not strong enough.
6. Validate after edits:
- Re-run this verifier.
- Run Home Assistant config validation before reload/restart.
6. Enforce closure:
7. Enforce closure:
- Treat unresolved `FULL_BLOCK`/`ENTRY` findings in touched files as incomplete work unless a blocker is documented.
- Prefer consolidating duplicated automation triggers/conditions/actions into shared logic or a single branching automation.
- Treat unresolved `CENTRAL_SCRIPT` findings in touched scope as incomplete unless documented as deferred-with-blocker.
- Move shared package scripts to `config/script/<script_id>.yaml` when they are used cross-file.
- Treat unresolved stale-reference or cleanup-safety concerns in touched scope as incomplete unless documented as `deferred-with-blocker`.
## Dashboard Designer Integration
@ -77,6 +89,8 @@ Always report:
- Script caller detection should include direct `service: script.<id>` and `script.turn_on`-style entity targeting when present.
- Concrete refactor recommendation per group.
- Resolution status for each finding (`resolved`, `deferred-with-blocker`).
- Entity/reference hygiene status when this pass is in scope (`checked`, `not-in-scope`, or `deferred-with-blocker`).
- For cleanup or destructive maintenance YAML, preview/dry-run, confirmation, and audit/backup status.
Strict behavior:
- `--strict` returns non-zero for any reported finding (`FULL_BLOCK`, `ENTRY`, `INTRA`, `CENTRAL_SCRIPT`).

@ -1,6 +1,6 @@
---
name: infrastructure-doc-sync
description: "Use when infra/container placement changes require synchronized AGENTS, docs, and Infra Info updates while keeping AGENTS concise, scoped, and non-runbook."
description: "Use when infra/container placement changes require synchronized AGENTS, docs, Dashy, and BearClaw infrastructure snapshot updates while keeping AGENTS concise, scoped, and non-runbook."
---
# Infrastructure Doc Sync
@ -31,8 +31,8 @@ Keep `AGENTS.md` short and task-scoped; move long runbooks to dedicated docs.
5. Dashy shortcuts (if any service URL/host changed):
- `h:\hass\docker_files\dashy/conf.yml`
- Reload Dashy on docker_17 after edits: `ssh hass@192.168.10.17 "cd ~/docker_files && docker compose up -d dashy"`
6. Infra Info snapshot JSON:
- `docker_69:/home/hass/docker_files/infra_info/data/overview.json`
6. BearClaw infrastructure snapshot:
- `docker_17/codex_appliance` environment map and `/api/admin/infrastructure`
## Workflow
@ -42,12 +42,13 @@ Keep `AGENTS.md` short and task-scoped; move long runbooks to dedicated docs.
4. Move long operational/runbook details out of `AGENTS.md` into a dedicated doc when needed.
5. If end-user entry points changed, update Dashy shortcuts (`dashy/conf.yml`) to match reality.
6. Update README/skill docs impacted by the change (short, factual, no drift).
7. Update `overview.json` to mirror the same outcome at a high level.
7. Refresh/check BearClaw infrastructure context so it mirrors the same outcome at a high level.
8. Validate:
- JSON is valid (`python -m json.tool` equivalent).
- Dashy `conf.yml` references the intended hostname(s)/ports (no stale LAN IPs unless intentionally required).
- AGENTS and README statements do not conflict with runtime.
- Repo-level AGENTS do not contain long runbooks duplicated from dedicated docs.
- BearClaw `/api/admin/infrastructure` returns the intended topology/context.
## AGENTS Quality Rules
@ -57,9 +58,9 @@ Keep `AGENTS.md` short and task-scoped; move long runbooks to dedicated docs.
- Keep specialized/deeper-scoped AGENTS concise and task-specific.
- De-duplicate repeated policy lines across global/workspace/repo scopes.
## Infra Info Content Rules
## BearClaw Snapshot Content Rules
- Keep `overview.json` high-level and planning-focused.
- Keep infrastructure snapshot content high-level and planning-focused.
- Do not include secrets, tokens, passwords, or internal file paths.
- Avoid step-by-step runbooks.
- Prefer host IDs and roles over low-level implementation detail.
@ -79,5 +80,6 @@ Always report:
- Final intended topology/placement.
- Any Dashy shortcuts touched (or explicitly state "no Dashy updates needed").
- Whether runbook content was moved from AGENTS into a dedicated ops doc.
- BearClaw infrastructure snapshot validation result.
- Any unresolved follow-up items.

@ -1,4 +1,4 @@
interface:
display_name: "Infrastructure Doc Sync"
short_description: "Sync infra docs with concise AGENTS"
default_prompt: "Use $infrastructure-doc-sync to keep AGENTS concise and scoped, move runbook content into dedicated docs when needed, and sync README/Dashy/infra_info overview.json after infra changes."
default_prompt: "Use $infrastructure-doc-sync to keep AGENTS concise and scoped, move runbook content into dedicated docs when needed, and sync README/Dashy/BearClaw infrastructure snapshot context after infra changes."

@ -50,11 +50,11 @@ flowchart TD
end
subgraph docker69[docker69]
Tunnel[Cloudflared]
Info[Infra Info]
PublicApps[Public Apps]
end
Tunnel --> Appliance
Tunnel --> Info
Tunnel --> PublicApps
HA --> MQTT
Frigate --> HA
```

@ -1 +1,2 @@
2026.5.3
2026.5.2

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 924 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save

Powered by TurnKey Linux.