feat: add submitted skills review desk

This commit is contained in:
Marcos Silva
2026-09-04 00:34:53 -03:00
parent a7034db94b
commit 5046fb580d
57 changed files with 3380 additions and 1 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
<body> <body>
<div class="reading-progress" aria-hidden="true"><span></span></div> <div class="reading-progress" aria-hidden="true"><span></span></div>
<main> <main>
<header class="topbar"><a class="brand" href="#top"><span class="mark">A</span> field guide</a><nav class="chapter-links" aria-label="Chapter sections"><a href="#fleet">01 fleet</a><a href="#worktrees">02 worktrees</a><a href="#models">03 models</a><a href="#skills">04 skills</a><a href="#create-skill">05 create</a><a href="#field-kit">06 field kit</a><a href="#hands-on">07 hands-on</a><a href="#verification">08 verify</a></nav><div class="topbar-tools"><div class="lang-switch" aria-label="Language"><button class="active" data-lang="en" aria-pressed="true">EN</button><span>/</span><button data-lang="pt" aria-pressed="false">PT</button></div><span class="edition">AI ENGINEERING <i></i> 01 / 2026</span></div></header> <header class="topbar"><a class="brand" href="#top"><span class="mark">A</span> field guide</a><nav class="chapter-links" aria-label="Chapter sections"><a href="#fleet">01 fleet</a><a href="#worktrees">02 worktrees</a><a href="#models">03 models</a><a href="#skills">04 skills</a><a href="#create-skill">05 create</a><a href="#field-kit">06 field kit</a><a href="#hands-on">07 hands-on</a><a href="#verification">08 verify</a></nav><div class="topbar-tools"><a class="skills-review-link" href="skills-review/">review submissions ↗</a><div class="lang-switch" aria-label="Language"><button class="active" data-lang="en" aria-pressed="true">EN</button><span>/</span><button data-lang="pt" aria-pressed="false">PT</button></div><span class="edition">AI ENGINEERING <i></i> 01 / 2026</span></div></header>
<section class="hero" id="top"><div><p class="eyebrow">A presentation for humans who ship</p><h1>AI for<br /><em>dummies.</em></h1><p class="lede">You do not need an army of models. You need a system: one mind to frame the work, several hands to execute it, and a clean boundary between every task.</p></div><aside class="hero-index"><span>FIELD NOTE / 001</span><strong>Ship the<br /><em>system.</em></strong><small>Skills · agents · worktrees · proof</small></aside></section> <section class="hero" id="top"><div><p class="eyebrow">A presentation for humans who ship</p><h1>AI for<br /><em>dummies.</em></h1><p class="lede">You do not need an army of models. You need a system: one mind to frame the work, several hands to execute it, and a clean boundary between every task.</p></div><aside class="hero-index"><span>FIELD NOTE / 001</span><strong>Ship the<br /><em>system.</em></strong><small>Skills · agents · worktrees · proof</small></aside></section>
<section class="hero-stats" aria-label="Chapter summary"><div><strong>01</strong><span>strong model<br />for ambiguity</span></div><div><strong>03</strong><span>bounded workers<br />in parallel</span></div><div><strong></strong><span>iterations<br />with evidence</span></div><p>Read this as a route map, not a prompt recipe.</p></section> <section class="hero-stats" aria-label="Chapter summary"><div><strong>01</strong><span>strong model<br />for ambiguity</span></div><div><strong>03</strong><span>bounded workers<br />in parallel</span></div><div><strong></strong><span>iterations<br />with evidence</span></div><p>Read this as a route map, not a prompt recipe.</p></section>
<section class="thesis"><div><span>RULE ZERO</span><strong>Strong model for ambiguity.<br />Light model for bounded work.</strong></div><div class="signal" aria-hidden="true"><b>THINK</b><i></i><i></i><i></i><b>MAKE</b></div></section> <section class="thesis"><div><span>RULE ZERO</span><strong>Strong model for ambiguity.<br />Light model for bounded work.</strong></div><div class="signal" aria-hidden="true"><b>THINK</b><i></i><i></i><i></i><b>MAKE</b></div></section>
+1
View File
@@ -1,4 +1,5 @@
.topbar-tools{display:flex;align-items:center;gap:24px} .topbar-tools{display:flex;align-items:center;gap:24px}
.skills-review-link{color:var(--blue);font:700 9px 'DM Mono',monospace;letter-spacing:.06em;text-decoration:none;text-transform:uppercase;white-space:nowrap}.skills-review-link:hover{color:var(--accent)}
.lang-switch{display:flex;align-items:center;gap:6px;color:var(--muted);font:500 10px 'DM Mono',monospace;letter-spacing:.1em} .lang-switch{display:flex;align-items:center;gap:6px;color:var(--muted);font:500 10px 'DM Mono',monospace;letter-spacing:.1em}
.lang-switch button{padding:0;border:0;color:inherit;background:transparent;font:inherit;cursor:pointer} .lang-switch button{padding:0;border:0;color:inherit;background:transparent;font:inherit;cursor:pointer}
.lang-switch button.active{color:var(--ink);font-weight:700} .lang-switch button.active{color:var(--ink);font-weight:700}
+10
View File
@@ -0,0 +1,10 @@
import { mkdirSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { catalog } from '../skills-review/catalog.js';
for (const entry of catalog) {
const output = join('skill-reviews', 'improved', entry.id, 'SKILL.md');
mkdirSync(dirname(output), { recursive: true });
writeFileSync(output, entry.improved);
}
console.log(`wrote ${catalog.length} improved skill drafts`);
+9
View File
@@ -11,6 +11,9 @@ const modelRouting = read('docs/references/model-routing.md');
const rulesHtml = read('rules/index.html'); const rulesHtml = read('rules/index.html');
const rulesJs = read('rules/app.js'); const rulesJs = read('rules/app.js');
const rulesCss = read('rules/styles.css'); const rulesCss = read('rules/styles.css');
const reviewHtml = read('skills-review/index.html');
const reviewJs = read('skills-review/app.js');
const reviewCatalog = read('skills-review/catalog.js');
for (const url of ['https://code.claude.com/docs/en/sub-agents','https://code.claude.com/docs/en/skills','https://code.claude.com/docs/en/worktrees','https://git-scm.com/docs/git-worktree.html','https://developers.openai.com/codex/skills']) if (!refs.includes(url)) throw new Error(`missing reference ${url}`); for (const url of ['https://code.claude.com/docs/en/sub-agents','https://code.claude.com/docs/en/skills','https://code.claude.com/docs/en/worktrees','https://git-scm.com/docs/git-worktree.html','https://developers.openai.com/codex/skills']) if (!refs.includes(url)) throw new Error(`missing reference ${url}`);
console.log('content verification passed'); console.log('content verification passed');
for (const token of ['data-phase="plan"','data-phase="build"','data-phase="review"','data-tree="main"','data-tree="ui"','data-worker="ui"','data-route="plan"','data-model-provider="openai"','data-model-provider="claude"','data-model-provider="gemini"','data-effort="low"','data-effort="medium"','data-effort="high"','data-skill-file="skill"','data-skill-step="observe"','data-skill-step="validate"','data-common-skill="ponytail"','data-common-skill="caveman"','data-common-skill="unlazy"','id="hands-on"','data-copy-target="prompt-install-skills"','data-copy-target="prompt-basic"','data-copy-target="prompt-skills"','hands-on/starter/','additional-reading.md','role="tablist"','<table']) if (!html.includes(token)) throw new Error(`missing content ${token}`); for (const token of ['data-phase="plan"','data-phase="build"','data-phase="review"','data-tree="main"','data-tree="ui"','data-worker="ui"','data-route="plan"','data-model-provider="openai"','data-model-provider="claude"','data-model-provider="gemini"','data-effort="low"','data-effort="medium"','data-effort="high"','data-skill-file="skill"','data-skill-step="observe"','data-skill-step="validate"','data-common-skill="ponytail"','data-common-skill="caveman"','data-common-skill="unlazy"','id="hands-on"','data-copy-target="prompt-install-skills"','data-copy-target="prompt-basic"','data-copy-target="prompt-skills"','hands-on/starter/','additional-reading.md','role="tablist"','<table']) if (!html.includes(token)) throw new Error(`missing content ${token}`);
@@ -30,6 +33,12 @@ for (const token of ['const languageCopy','const stages','const skills','const p
for (const token of ['data-lang="en"','data-lang="pt"','data-copy-prompt','aria-live="polite"','role="tablist"']) if (!rulesHtml.includes(token)) throw new Error(`missing rules control ${token}`); for (const token of ['data-lang="en"','data-lang="pt"','data-copy-prompt','aria-live="polite"','role="tablist"']) if (!rulesHtml.includes(token)) throw new Error(`missing rules control ${token}`);
console.log('rules interaction verification passed'); console.log('rules interaction verification passed');
if (!html.includes('href="rules/"')) throw new Error('main presentation does not link to rules page'); if (!html.includes('href="rules/"')) throw new Error('main presentation does not link to rules page');
if (!html.includes('href="skills-review/"')) throw new Error('main presentation does not link to skills review page');
if (rulesHtml.includes('script src="http') || rulesHtml.includes('rel="stylesheet" href="http')) throw new Error('rules page has an external runtime dependency'); if (rulesHtml.includes('script src="http') || rulesHtml.includes('rel="stylesheet" href="http')) throw new Error('rules page has an external runtime dependency');
for (const token of ['@media(min-width:2200px)','@media(max-width:900px)','@media(max-width:600px)','prefers-reduced-motion']) if (!rulesCss.includes(token)) throw new Error(`missing rules responsive contract ${token}`); for (const token of ['@media(min-width:2200px)','@media(max-width:900px)','@media(max-width:600px)','prefers-reduced-motion']) if (!rulesCss.includes(token)) throw new Error(`missing rules responsive contract ${token}`);
console.log('rules standalone verification passed'); console.log('rules standalone verification passed');
for (const token of ['id="catalog"','id="skill-filter"','id="skill-list"','id="detail"','?author=Name&amp;skill=skill-id&amp;view=improved']) if (!reviewHtml.includes(token)) throw new Error(`missing review page content ${token}`);
for (const token of ["from './catalog.js'",'function renderList','function renderDetail','function original','function selectFromUrl','function syncUrl','URLSearchParams','navigator.clipboard','document.execCommand','download']) if (!reviewJs.includes(token)) throw new Error(`missing review interaction ${token}`);
if ((reviewCatalog.match(/id:'/g) || []).length !== 16) throw new Error('review catalog does not cover all submissions');
if (!reviewCatalog.includes('hardcoded password') || !reviewCatalog.includes('safety-redacted') || !reviewJs.includes('[REDACTED]')) throw new Error('review catalog does not record secret safety handling');
console.log('skills review verification passed');
+19
View File
@@ -0,0 +1,19 @@
---
name: am-i-free
description: Calculate elapsed work time from the Long Day Factory shift record. Use when the user asks whether they can leave or how much time remains.
---
# am-i-free
## Workflow
1. Run `python3 scripts/am_i_free.py`.
2. Interpret its documented exit code. Ask before any option that writes an assumed lunch break.
3. Give the result, remaining time or release time, and a concise friendly message.
## Rules
- Treat malformed or missing state as a recovery question, not a calculation.
- Read `references/state.md` for schema and timezone behavior.
- Do not expose unrelated content from the local state file.
## Output
Report calculation status, remaining time or freedom, and any assumption made.
@@ -0,0 +1,19 @@
---
name: angular-access-modifiers
description: Apply explicit TypeScript access modifiers to Angular component, directive, and pipe members. Use when editing or reviewing Angular class APIs in a repository that adopts this convention.
---
# angular-access-modifiers
## Workflow
1. Inspect the components template and callers before changing visibility.
2. Use `protected` for template-facing members when the project supports it, `private` for implementation details, and `public` for intentional external APIs and lifecycle hooks.
3. Keep existing framework-required visibility when a compiler or decorator requires it.
4. Run the project typecheck and relevant template tests.
## Rules
- Do not change visibility only to satisfy a test; fix the test boundary or document the API.
- Prefer the repositorys established Angular convention if it differs.
## Output
List changed members, their consumers, and verification results.
@@ -0,0 +1,20 @@
---
name: angular-accessibility
description: Build and review Angular interfaces for accessible semantics, keyboard use, focus behavior, and clear status feedback. Use when changing Angular templates, forms, dialogs, navigation, or custom controls.
---
# angular-accessibility
## Workflow
1. Inspect the changed interaction and choose native semantic elements first.
2. Check keyboard operation, focus order, visible focus, labels, errors, and dynamic announcements.
3. Use Angular CDK or Material primitives when they provide the expected behavior.
4. Run available accessibility checks and manually test the changed interaction by keyboard.
## Rules
- ARIA supplements native semantics; it does not replace them.
- Do not claim WCAG conformance from one review.
- Read `references/patterns.md` only for dialogs, tables, or custom composite controls.
## Output
Return changed issues, evidence, and any remaining manual checks.
@@ -0,0 +1,20 @@
---
name: angular-accessibility
description: Build and review Angular interfaces for accessible semantics, keyboard use, focus behavior, and clear status feedback. Use when changing Angular templates, forms, dialogs, navigation, or custom controls.
---
# angular-accessibility
## Workflow
1. Inspect the changed interaction and choose native semantic elements first.
2. Check keyboard operation, focus order, visible focus, labels, errors, and dynamic announcements.
3. Use Angular CDK or Material primitives when they provide the expected behavior.
4. Run available accessibility checks and manually test the changed interaction by keyboard.
## Rules
- ARIA supplements native semantics; it does not replace them.
- Do not claim WCAG conformance from one review.
- Read `references/patterns.md` only for dialogs, tables, or custom composite controls.
## Output
Return changed issues, evidence, and any remaining manual checks.
@@ -0,0 +1,18 @@
---
name: back-to-work
description: Record the return time for a Long Day Factory lunch break. Use when the user says they have returned to work.
---
# back-to-work
## Workflow
1. Confirm the message is an instruction to record the current return time.
2. Run `bash scripts/back.sh`.
3. Surface any missing shift or lunch state and explain the next recovery action.
## Rules
- This command changes local shift state; do not run it for a hypothetical question.
- Use the shared state schema in `references/state.md`.
## Output
Confirm the recorded timestamp and any state warning with a light, respectful tone.
@@ -0,0 +1,23 @@
---
name: backend-code-reviewer
description: Review a scoped backend change for evidenced security, reliability, data-access, and API-boundary risks. Use when reviewing a backend diff; do not install tools or modify CI unless the user asks.
---
# backend-code-reviewer
## Inputs
A branch diff or changed backend paths and the projects declared tooling.
## Workflow
1. Identify runtime, framework, and existing checks from the repository.
2. Review changed data access, async boundaries, error handling, API contracts, secrets, and resource limits.
3. Report findings only when a concrete path and consequence are visible; label hypotheses separately.
4. Run existing, approved checks and include their evidence.
## Rules
- Do not download or pipe remote installers into a shell.
- Do not claim missing indexes, retries, or architectural violations without repository evidence.
- Read `references/rules.md` for framework-specific checks.
## Output
Return severity, location, evidence, impact, recommendation, and checks run.
@@ -0,0 +1,22 @@
---
name: code-style-review
description: Run the configured formatter and static checks for a scoped code change. Use after editing code or before a review; discover project commands rather than assuming a stack.
---
# code-style-review
## Inputs
Changed files and the repository root.
## Workflow
1. Inspect package/build configuration for the projects documented lint, format, and style commands.
2. Run the narrowest relevant check first. Apply formatting only to the requested files unless the user asks for a wider change.
3. Review the diff for accidental rewrites, then rerun the same checks.
## Rules
- Do not invent directories or install tools without approval.
- Report unavailable checks as not run, not passed.
- Treat unused-code removal as a separate semantic change.
## Output
List each command, result, changed files, and any remaining failure.
@@ -0,0 +1,20 @@
---
name: codebase-map
description: Maintain FEATURE_MAP.md as a concise, verified index of feature entry points. Use before locating code for a change and after a change moves or adds an entry point.
---
# codebase-map
## Workflow
1. If `FEATURE_MAP.md` exists, check whether the relevant entry path still exists.
2. Use a valid entry as the starting point; otherwise search normally.
3. After locating the feature, update the existing entry or add one concise entry point.
4. Run `scripts/check-feature-map.mjs` when available.
## Rules
- Preserve a stale entry until a replacement is known, then update it in the same edit.
- Index features and flows, not every file.
- Do not make map edits when a change leaves entry points unchanged.
## Output
State whether the map was used, changed, or unavailable.
@@ -0,0 +1,23 @@
---
name: confectionery-orders
description: Create or prepare a confectionery order from confirmed customer and item details. Use when a user asks to register an order or counter sale; confirm before sending it to an external system.
---
# confectionery-orders
## Inputs
Customer, pickup or delivery choice, items, quantities, prices, and optional discount.
## Workflow
1. Validate required fields and positive quantities.
2. Calculate the proposed total and show a concise order summary.
3. Ask for confirmation before creating or transmitting an order.
4. Return the saved identifier or a clearly labeled draft.
## Rules
- Do not invent recipe availability, prices, addresses, or customer details.
- Keep payment and personal data out of logs.
- Read `references/order-schema.md` when mapping to the order system.
## Output
Return a valid order payload plus validation warnings and confirmation state.
@@ -0,0 +1,24 @@
---
name: copy-quote-info-to-payload
description: Populate a quote-command skeleton from quote data without fabricating values. Use when a quote JSON and command skeleton are supplied and the user asks to create a populated command.
---
# copy-quote-info-to-payload
## Inputs
One source quote JSON and one target command skeleton.
## Workflow
1. Identify source and target; ask when the roles are ambiguous.
2. Parse both documents and start from the target structure.
3. Apply the mappings in `reference.md`; preserve unmatched target fields and item order.
4. Validate that the resulting document is valid JSON.
5. Return the payload and a short mapping summary.
## Rules
- Every populated value must come from the source or an explicit user instruction.
- Never silently choose between duplicate IDs or conflicting values.
- Do not alter item content unless the user requests it.
## Output
Return one valid JSON document, then unresolved placeholders and mapping warnings.
@@ -0,0 +1,22 @@
---
name: duplicate-code-check
description: Review a branch or merge-request diff for newly introduced, meaningful code duplication. Use when a user asks about repeated logic or copy-paste code in a diff.
---
# duplicate-code-check
## Inputs
Source branch or MR and target branch; use the repository default base only after reporting it.
## Workflow
1. Obtain the merge-base diff and list files examined.
2. Compare changed blocks with nearby and existing code; distinguish deliberate repetition, generated code, and test fixtures.
3. Report evidenced candidates with both locations, similarity, maintenance risk, and a proportionate suggestion.
## Rules
- Do not modify or remove code without explicit approval.
- Do not label repeated literals alone as duplication without a maintenance consequence.
- Report scope limits and skipped generated files.
## Output
Return a Markdown table: candidate, locations, evidence, confidence, risk, suggested next step.
@@ -0,0 +1,23 @@
---
name: generated-code-explanation
description: Explain a code change, its supported rationale, trade-offs, and verification for a named audience. Use when a user asks what changed, why it changed, or how to validate it.
---
# generated-code-explanation
## Inputs
A diff, files, or a confirmed description of the change; intended audience.
## Workflow
1. Read the supplied code or diff before making claims.
2. Explain behavior first, then the evidence-backed reason and trade-offs.
3. Adapt vocabulary and depth to the audience.
4. State verification that was run and checks that remain.
## Rules
- Mark unknown intent as unknown; do not infer motivation.
- Do not add comments or documentation only to make an explanation easier.
- Read project-specific conventions from a reference only in that project.
## Output
Use: What changed, Why this approach, Trade-offs, How to verify.
@@ -0,0 +1,18 @@
---
name: long-day-start
description: Start a Long Day Factory shift by recording the current time and clearing lunch state. Use when the user explicitly says they have started their day.
---
# long-day-start
## Workflow
1. Check whether an incomplete shift record exists.
2. If it does, explain that starting a new shift replaces its lunch state and ask for confirmation.
3. Run `bash scripts/start.sh` after explicit start authorization.
## Rules
- Do not reset a shift for a hypothetical or informational request.
- Store and document times in timezone-aware ISO 8601 format.
## Output
Confirm the new start timestamp and whether a prior shift was replaced.
@@ -0,0 +1,18 @@
---
name: lunch-time
description: Record the start of a Long Day Factory lunch break. Use when the user explicitly says they are starting lunch.
---
# lunch-time
## Workflow
1. Confirm the request records a lunch start now.
2. Check for a started shift and an existing open lunch.
3. If an open lunch exists, ask before replacing it; otherwise run `bash scripts/lunch.sh`.
## Rules
- This command changes local state; do not run it for a question about lunch time.
- Use `references/state.md` for recovery rules.
## Output
Confirm the lunch timestamp and any missing or conflicting state.
+23
View File
@@ -0,0 +1,23 @@
---
name: ndo-repro
description: Reproduce or validate an NDO issue through approved local build, dev-environment deployment, BOM API calls, and live logs. Use only when the user names the service and target environment.
---
# ndo-repro
## Safety boundary
Read-only diagnosis is allowed after environment selection. Build, push, deploy, rollback, and credential changes require explicit approval for the named environment and action.
## Workflow
1. Run `scripts/doctor.sh` and resolve the environment using the bundled registry.
2. Build and test locally; confirm the exact image reference.
3. Before a shared-environment mutation, restate service, environment, image, and rollback plan; wait for approval.
4. Drive the smallest API flow that tests the acceptance criterion, then collect image, response, and log evidence.
## Rules
- Read credentials from approved environment variables or a secret manager; never embed or echo them.
- Use paths relative to this package.
- Do not infer a pass from a nearby signal.
## Output
Report approval, deployed image, criterion-by-criterion evidence, and untested criteria.
@@ -0,0 +1,23 @@
---
name: sql-injection-audit
description: Audit a changed code path for SQL injection. Use when code constructs or executes SQL, query-builder fragments, or ORM raw queries.
---
# sql-injection-audit
## Inputs
Changed files, branch diff, or a named query path.
## Workflow
1. Find SQL execution sinks and trace request, CLI, external, and stored user input to them.
2. Confirm values use driver or ORM parameters. For dynamic identifiers, confirm a finite allowlist maps a user choice to a trusted token.
3. Review raw-query escape hatches and stored procedures.
4. Report only evidenced findings with source, sink, location, impact, and a safe pattern.
## Rules
- Escaping is not a substitute for parameterization.
- Passing tests are supporting evidence, not proof of safety.
- Do not modify code unless the user asks for a fix.
## Output
Return a findings table and the scope reviewed; say explicitly when a path could not be traced.
+60
View File
@@ -0,0 +1,60 @@
import { catalog } from './catalog.js';
const state = { selected: catalog[0], query: '', preview: 'original', source: '' };
const $ = (selector) => document.querySelector(selector);
const escape = (value) => value.replace(/[&<>"']/g, (character) => ({ '&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', "'":'&#039;' })[character]);
const redact = (value) => value.replace(/(NDO_PASS[^\n=]*[=:]\s*["']?)[^\n"']+/gi, '$1[REDACTED]').replace(/(password["']?\s*[:=]\s*["']?)[^\n"']+/gi, '$1[REDACTED]').replace(/\bsysadm@netcracker\.com\b/gi, '[REDACTED SERVICE ACCOUNT]');
const download = (name, content) => { const url = URL.createObjectURL(new Blob([content], { type: 'text/markdown' })); const a = document.createElement('a'); a.href = url; a.download = name; a.click(); URL.revokeObjectURL(url); };
const copy = async (content) => {
if (navigator.clipboard?.writeText) return navigator.clipboard.writeText(content);
const textarea = document.createElement('textarea'); textarea.value = content; textarea.setAttribute('readonly', ''); textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.append(textarea); textarea.select(); document.execCommand('copy'); textarea.remove();
};
function visible() { return catalog.filter((item) => `${item.author} ${item.title} ${item.focus}`.toLowerCase().includes(state.query)); }
function syncUrl() {
const url = new URL(window.location.href);
url.searchParams.set('author', state.selected.author);
url.searchParams.set('skill', state.selected.id);
url.searchParams.set('view', state.preview);
history.replaceState({}, '', url);
}
function selectFromUrl() {
const params = new URLSearchParams(window.location.search);
const author = params.get('author');
const id = params.get('skill');
const view = params.get('view');
const byAuthor = author && catalog.filter((item) => item.author.toLowerCase() === author.toLowerCase());
const byId = id && catalog.find((item) => item.id === id);
state.selected = byId || byAuthor?.[0] || catalog[0];
state.query = byAuthor ? state.selected.author.toLowerCase() : '';
state.preview = view === 'improved' ? 'improved' : 'original';
$('#skill-filter').value = byAuthor ? state.selected.author : '';
}
function renderList() {
const items = visible();
$('#count').textContent = `${items.length} of ${catalog.length} reviewed`;
$('#skill-list').innerHTML = items.map((item) => `<button role="option" aria-selected="${item.id === state.selected.id}" class="${item.id === state.selected.id ? 'active' : ''}" data-id="${item.id}"><span>${escape(item.author)}</span><strong>${escape(item.title)}</strong><small>${escape(item.status)}</small></button>`).join('');
$('#skill-list').querySelectorAll('button').forEach((button) => button.addEventListener('click', () => { state.selected = catalog.find((item) => item.id === button.dataset.id); state.preview = 'original'; state.source = ''; syncUrl(); renderList(); renderDetail(); }));
}
async function original(entry) {
if (state.source) return state.source;
try { state.source = redact(await (await fetch(entry.path)).text()); } catch { state.source = '# Original preview unavailable\n\nServe this site from the repository root to load the submitted source.'; }
renderDetail();
return state.source;
}
function renderDetail() {
const entry = state.selected; const markdown = state.preview === 'original' ? (state.source || 'Loading original Markdown…') : entry.improved;
$('#detail').innerHTML = `<header><div><span class="status">${escape(entry.status)}</span><h2>${escape(entry.title)}</h2><p>Submitted by <a class="author-link" href="?author=${encodeURIComponent(entry.author)}">${escape(entry.author)}</a> · <a class="share-link" href="?author=${encodeURIComponent(entry.author)}&skill=${encodeURIComponent(entry.id)}&view=${state.preview}">share review ↗</a></p></div><div class="switch" role="group" aria-label="Preview version"><button class="${state.preview === 'original' ? 'active' : ''}" data-preview="original">Original</button><button class="${state.preview === 'improved' ? 'active' : ''}" data-preview="improved">Improved draft</button></div></header>
<div class="purpose"><span>THE JOB</span><p>${escape(entry.focus)}</p></div>
<div class="review-grid"><section><span>WHAT'S ALREADY WORKING</span><ul>${entry.wins.map((item) => `<li>${escape(item)}</li>`).join('')}</ul></section><section><span>HIGHEST-VALUE IMPROVEMENTS</span><ul>${entry.improve.map((item) => `<li>${escape(item)}</li>`).join('')}</ul></section></div>
<aside class="extras"><span>GOOD NEXT ADDITION</span>${escape(entry.extras)}</aside>
<section class="preview"><header><span>${state.preview === 'original' ? 'ORIGINAL / SAFETY-REDACTED WHERE NEEDED' : 'IMPROVED DRAFT / READY TO ADAPT'}</span><div><button data-copy>Copy</button><button data-download>Download</button></div></header><pre><code>${escape(markdown)}</code></pre></section>`;
$('#detail').querySelectorAll('[data-preview]').forEach((button) => button.addEventListener('click', () => { state.preview = button.dataset.preview; state.source = state.preview === 'original' ? state.source : ''; syncUrl(); renderDetail(); if (state.preview === 'original') original(entry); }));
$('[data-copy]').addEventListener('click', async () => { const content = state.preview === 'original' ? await original(entry) : entry.improved; await copy(content); $('[data-copy]').textContent = 'Copied'; });
$('[data-download]').addEventListener('click', async () => download(`${entry.id}-${state.preview}.md`, state.preview === 'original' ? await original(entry) : entry.improved));
if (state.preview === 'original' && !state.source) original(entry);
}
$('#skill-filter').addEventListener('input', (event) => { state.query = event.target.value.toLowerCase().trim(); renderList(); });
window.addEventListener('popstate', () => { state.source = ''; selectFromUrl(); renderList(); renderDetail(); });
selectFromUrl();
renderList(); renderDetail();
+28
View File
@@ -0,0 +1,28 @@
export const sources = {
specification: 'https://agentskills.io/specification',
practices: 'https://agentskills.io/skill-creation/best-practices',
descriptions: 'https://agentskills.io/skill-creation/optimizing-descriptions',
evaluation: 'https://agentskills.io/skill-creation/evaluating-skills',
scripts: 'https://agentskills.io/skill-creation/using-scripts'
};
const skill = (name, description, body) => `---\nname: ${name}\ndescription: ${description}\n---\n\n# ${name}\n\n${body.trim()}\n`;
export const catalog = [
{ id:'code-style-review', author:'Andre Oliveira', path:'../submitted-skills/Andre%20Oliveira/skills/code-style-review/SKILL.md', title:'Code style review', status:'Good foundation', focus:'Run the repositorys configured formatter and static checks after a scoped code change.', wins:['Clear timing: after changes and before review.','Includes a final evidence checklist.'], improve:['Do not assume `backend/`, `frontend/`, Maven, ESLint, or Prettier exist; discover scripts from the current repository first.','Separate safe formatting from semantic cleanup and require a diff review before broad auto-fixes.','Add a small command-discovery script only if this project repeats the lookup.'], extras:'Add `references/tooling.md` only for known project commands; add one eval for a repo without either folder.', improved:skill('code-style-review','Run the configured formatter and static checks for a scoped code change. Use after editing code or before a review; discover project commands rather than assuming a stack.',`## Inputs\nChanged files and the repository root.\n\n## Workflow\n1. Inspect package/build configuration for the projects documented lint, format, and style commands.\n2. Run the narrowest relevant check first. Apply formatting only to the requested files unless the user asks for a wider change.\n3. Review the diff for accidental rewrites, then rerun the same checks.\n\n## Rules\n- Do not invent directories or install tools without approval.\n- Report unavailable checks as not run, not passed.\n- Treat unused-code removal as a separate semantic change.\n\n## Output\nList each command, result, changed files, and any remaining failure.`)},
{ id:'sql-injection-audit', author:'Andre Salvo', path:'../submitted-skills/Andre%20Salvo/skills/sql-injection-audit/SKILL.md', title:'SQL injection audit', status:'Fix metadata', focus:'Trace user-controlled data to SQL sinks and verify values are parameterized.', wins:['Strong threat-model coverage, including identifiers and second-order injection.','The report asks for source, sink, and data flow.'], improve:['The frontmatter is invalid because an un-keyed line appears inside it; fix this first so hosts can discover the skill.','Scope the audit to changed code or named paths by default to avoid an unbounded repository scan.','Add language-specific safe/unsafe examples in a reference rather than expanding the main file.'], extras:'Add an eval with a parameterized query and a dynamic `ORDER BY` allowlist.', improved:skill('sql-injection-audit','Audit a changed code path for SQL injection. Use when code constructs or executes SQL, query-builder fragments, or ORM raw queries.',`## Inputs\nChanged files, branch diff, or a named query path.\n\n## Workflow\n1. Find SQL execution sinks and trace request, CLI, external, and stored user input to them.\n2. Confirm values use driver or ORM parameters. For dynamic identifiers, confirm a finite allowlist maps a user choice to a trusted token.\n3. Review raw-query escape hatches and stored procedures.\n4. Report only evidenced findings with source, sink, location, impact, and a safe pattern.\n\n## Rules\n- Escaping is not a substitute for parameterization.\n- Passing tests are supporting evidence, not proof of safety.\n- Do not modify code unless the user asks for a fix.\n\n## Output\nReturn a findings table and the scope reviewed; say explicitly when a path could not be traced.`)},
{ id:'confectionary-skill-hub', author:'Diego Moreira', path:'../submitted-skills/Diego%20Moreira/skills/confectionary-skill-hub/SKILL.md', title:'Confectionery skill hub', status:'Split required', focus:'Define recipe and order workflows for a confectionery domain.', wins:['Useful domain vocabulary and input shapes.','Concrete examples make the intent easy to understand.'], improve:['This is a catalog of three capabilities, not one discoverable skill; split recipe creation, recipe search, and order creation into packages.','Add valid frontmatter and state the system of record, validation rules, and mutation approval boundary.','Move JSON schemas to focused references so only the relevant workflow loads.'], extras:'Add `references/recipe-schema.md` and `references/order-schema.md`; test invalid quantities and missing delivery details.', improved:skill('confectionery-orders','Create or prepare a confectionery order from confirmed customer and item details. Use when a user asks to register an order or counter sale; confirm before sending it to an external system.',`## Inputs\nCustomer, pickup or delivery choice, items, quantities, prices, and optional discount.\n\n## Workflow\n1. Validate required fields and positive quantities.\n2. Calculate the proposed total and show a concise order summary.\n3. Ask for confirmation before creating or transmitting an order.\n4. Return the saved identifier or a clearly labeled draft.\n\n## Rules\n- Do not invent recipe availability, prices, addresses, or customer details.\n- Keep payment and personal data out of logs.\n- Read \`references/order-schema.md\` when mapping to the order system.\n\n## Output\nReturn a valid order payload plus validation warnings and confirmation state.`)},
{ id:'angular-access-modifiers', author:'Francisco Rangel', path:'../submitted-skills/Francisco%20Rangel/skills/angular-access-modifier/SKILL.md', title:'Angular access modifiers', status:'Sharpen scope', focus:'Make Angular class visibility explicit while respecting template and public APIs.', wins:['The template/private/public decision table is memorable.','Examples teach the preferred result.'], improve:['“Every member must be explicit” should be validated against the repositorys TypeScript and Angular version/conventions.','Do not assume tests require `public`; distinguish real external access from test workarounds.','Add a verification step using the project typecheck and template compiler.'], extras:'A lightweight AST check could prevent repeated manual review; include only if the convention is team-wide.', improved:skill('angular-access-modifiers','Apply explicit TypeScript access modifiers to Angular component, directive, and pipe members. Use when editing or reviewing Angular class APIs in a repository that adopts this convention.',`## Workflow\n1. Inspect the components template and callers before changing visibility.\n2. Use \`protected\` for template-facing members when the project supports it, \`private\` for implementation details, and \`public\` for intentional external APIs and lifecycle hooks.\n3. Keep existing framework-required visibility when a compiler or decorator requires it.\n4. Run the project typecheck and relevant template tests.\n\n## Rules\n- Do not change visibility only to satisfy a test; fix the test boundary or document the API.\n- Prefer the repositorys established Angular convention if it differs.\n\n## Output\nList changed members, their consumers, and verification results.`)},
{ id:'codebase-map', author:'Guilherme Lobo', path:'../submitted-skills/Guilherme%20Lobo/skills/codebase-map/SKILL.md', title:'Codebase map', status:'Strong candidate', focus:'Maintain a small, trustworthy index of feature entry points.', wins:['Excellent narrow purpose and stale-entry handling.','Clear rule for lazy, cheap maintenance.'], improve:['Avoid deleting a stale entry before verifying the replacement location; update atomically instead.','Define ownership and a conflict strategy for map edits in busy repositories.','Add a check that every listed path exists, rather than requiring an agent to remember it.'], extras:'A `scripts/check-feature-map.mjs` validator is justified because the invariant is deterministic.', improved:skill('codebase-map','Maintain FEATURE_MAP.md as a concise, verified index of feature entry points. Use before locating code for a change and after a change moves or adds an entry point.',`## Workflow\n1. If \`FEATURE_MAP.md\` exists, check whether the relevant entry path still exists.\n2. Use a valid entry as the starting point; otherwise search normally.\n3. After locating the feature, update the existing entry or add one concise entry point.\n4. Run \`scripts/check-feature-map.mjs\` when available.\n\n## Rules\n- Preserve a stale entry until a replacement is known, then update it in the same edit.\n- Index features and flows, not every file.\n- Do not make map edits when a change leaves entry points unchanged.\n\n## Output\nState whether the map was used, changed, or unavailable.`)},
{ id:'angular-accessibility-root', author:'Leonardo Uno', path:'../submitted-skills/Leonardo%20Uno/SKILL.md', title:'Angular accessibility (root copy)', status:'Duplicate package', focus:'Build and review Angular UIs against WCAG 2.2 AA.', wins:['Prioritizes native semantics before ARIA.','Covers interaction, focus, forms, and live updates.'], improve:['This is a duplicate of the nested package; retain only one canonical location to avoid drift.','Make “WCAG 2.2 AA” an audit target, not a claim of guaranteed compliance.','Add a small test matrix and route detailed component patterns to references.'], extras:'Keep one canonical package under `skills/angular-accessibility/` and add an eval for a keyboard-only dialog.', improved:skill('angular-accessibility','Build and review Angular interfaces for accessible semantics, keyboard use, focus behavior, and clear status feedback. Use when changing Angular templates, forms, dialogs, navigation, or custom controls.',`## Workflow\n1. Inspect the changed interaction and choose native semantic elements first.\n2. Check keyboard operation, focus order, visible focus, labels, errors, and dynamic announcements.\n3. Use Angular CDK or Material primitives when they provide the expected behavior.\n4. Run available accessibility checks and manually test the changed interaction by keyboard.\n\n## Rules\n- ARIA supplements native semantics; it does not replace them.\n- Do not claim WCAG conformance from one review.\n- Read \`references/patterns.md\` only for dialogs, tables, or custom composite controls.\n\n## Output\nReturn changed issues, evidence, and any remaining manual checks.`)},
{ id:'angular-accessibility', author:'Leonardo Uno', path:'../submitted-skills/Leonardo%20Uno/skills/angular-accessibility/SKILL.md', title:'Angular accessibility', status:'Needs consolidation', focus:'Build and review Angular UIs against WCAG 2.2 AA.', wins:['The most complete submitted accessibility guidance.','Clear examples for native controls and labels.'], improve:['Use this as the canonical copy and remove the root duplicate.','Move long component examples into a reference so the active instructions stay task-focused.','Add testing commands only when the repository declares axe, Lighthouse, or Angular test support.'], extras:'Add a test matrix for keyboard, screen reader announcement, error association, and contrast evidence.', improved:skill('angular-accessibility','Build and review Angular interfaces for accessible semantics, keyboard use, focus behavior, and clear status feedback. Use when changing Angular templates, forms, dialogs, navigation, or custom controls.',`## Workflow\n1. Inspect the changed interaction and choose native semantic elements first.\n2. Check keyboard operation, focus order, visible focus, labels, errors, and dynamic announcements.\n3. Use Angular CDK or Material primitives when they provide the expected behavior.\n4. Run available accessibility checks and manually test the changed interaction by keyboard.\n\n## Rules\n- ARIA supplements native semantics; it does not replace them.\n- Do not claim WCAG conformance from one review.\n- Read \`references/patterns.md\` only for dialogs, tables, or custom composite controls.\n\n## Output\nReturn changed issues, evidence, and any remaining manual checks.`)},
{ id:'copy-quote-info-to-payload', author:'Lucas Mantovan', path:'../submitted-skills/Lucas%20Mantovan/skills/copy-quote-info-to-payload/SKILL.md', title:'Copy quote info to payload', status:'Very strong', focus:'Map source quote data into a target command without inventing data.', wins:['Excellent source/skeleton distinction and preservation rule.','Uses a linked, on-demand mapping reference.'], improve:['Add a machine-checkable JSON validation step before returning output.','Define behavior for duplicate IDs, unmatched items, and conflicting values in the source.','Provide a fixture-based transform script if this exact mapping is repeatedly performed.'], extras:'Add an eval for missing values and a different skeleton shape; assert returned JSON parses.', improved:skill('copy-quote-info-to-payload','Populate a quote-command skeleton from quote data without fabricating values. Use when a quote JSON and command skeleton are supplied and the user asks to create a populated command.',`## Inputs\nOne source quote JSON and one target command skeleton.\n\n## Workflow\n1. Identify source and target; ask when the roles are ambiguous.\n2. Parse both documents and start from the target structure.\n3. Apply the mappings in \`reference.md\`; preserve unmatched target fields and item order.\n4. Validate that the resulting document is valid JSON.\n5. Return the payload and a short mapping summary.\n\n## Rules\n- Every populated value must come from the source or an explicit user instruction.\n- Never silently choose between duplicate IDs or conflicting values.\n- Do not alter item content unless the user requests it.\n\n## Output\nReturn one valid JSON document, then unresolved placeholders and mapping warnings.`)},
{ id:'generated-code-explanation', author:'Matheus Rocha', path:'../submitted-skills/Matheus%20Rocha/skills/generated-code-explanation/SKILL.md', title:'Generated code explanation', status:'Good writing guide', focus:'Explain changed code faithfully for the intended reader.', wins:['The what/why/verify structure is clear.','Explicitly prohibits invented rationale.'], improve:['The named demo-project module paths make the skill trigger too broadly outside that project; move them to a project reference.','Ask for the diff or paths before explaining an unprovided change.','Avoid requiring “alternatives considered” unless evidence supports them.'], extras:'Add a reviewer and non-technical audience eval to prove the explanation adapts without speculation.', improved:skill('generated-code-explanation','Explain a code change, its supported rationale, trade-offs, and verification for a named audience. Use when a user asks what changed, why it changed, or how to validate it.',`## Inputs\nA diff, files, or a confirmed description of the change; intended audience.\n\n## Workflow\n1. Read the supplied code or diff before making claims.\n2. Explain behavior first, then the evidence-backed reason and trade-offs.\n3. Adapt vocabulary and depth to the audience.\n4. State verification that was run and checks that remain.\n\n## Rules\n- Mark unknown intent as unknown; do not infer motivation.\n- Do not add comments or documentation only to make an explanation easier.\n- Read project-specific conventions from a reference only in that project.\n\n## Output\nUse: What changed, Why this approach, Trade-offs, How to verify.`)},
{ id:'ndo-repro', author:'Pedro Aranha', path:'../submitted-skills/Pedro%20Aranha/skills/ndo-repro/SKILL.md', title:'NDO reproduce loop', status:'Security action required', focus:'Build, deploy, and verify a microservice against a dev environment.', wins:['Exceptionally concrete workflow, evidence standard, rollback path, and approval gate.','Bundled scripts and focused operational references are appropriate.'], improve:['A hardcoded password is present in a bundled script. Remove it immediately, rotate it, and read credentials only from an approved secret source.','Use package-relative script paths instead of a host-specific `~/.claude` location.','Separate read-only investigation from shared-environment deploy actions in the header and require explicit per-environment approval.'], extras:'Add `scripts/doctor.sh` for dependency and credential-presence checks, plus a safe dry-run deploy eval. Original preview is safety-redacted.', improved:skill('ndo-repro','Reproduce or validate an NDO issue through approved local build, dev-environment deployment, BOM API calls, and live logs. Use only when the user names the service and target environment.',`## Safety boundary\nRead-only diagnosis is allowed after environment selection. Build, push, deploy, rollback, and credential changes require explicit approval for the named environment and action.\n\n## Workflow\n1. Run \`scripts/doctor.sh\` and resolve the environment using the bundled registry.\n2. Build and test locally; confirm the exact image reference.\n3. Before a shared-environment mutation, restate service, environment, image, and rollback plan; wait for approval.\n4. Drive the smallest API flow that tests the acceptance criterion, then collect image, response, and log evidence.\n\n## Rules\n- Read credentials from approved environment variables or a secret manager; never embed or echo them.\n- Use paths relative to this package.\n- Do not infer a pass from a nearby signal.\n\n## Output\nReport approval, deployed image, criterion-by-criterion evidence, and untested criteria.`)},
{ id:'duplicate-code-check', author:'Tatyana Ardyntceva', path:'../submitted-skills/Tatyana%20Ardyntceva/skills/duplicate-code-check/SKILL.md', title:'Duplicate code check', status:'Needs report contract', focus:'Find duplication newly introduced by a branch or merge-request diff.', wins:['Appropriately non-mutating by default.','Targets the diff rather than all code.'], improve:['“Ask before suggesting removal” is unnecessarily restrictive: suggestions are useful; ask before modifying code instead.','Define the diff base/default when branch details are missing.','Use a report with location pairs, similarity evidence, confidence, and a “do not merge” threshold.'], extras:'Add a script for obtaining the merge-base diff and an eval with intentional repeated test fixture code.', improved:skill('duplicate-code-check','Review a branch or merge-request diff for newly introduced, meaningful code duplication. Use when a user asks about repeated logic or copy-paste code in a diff.',`## Inputs\nSource branch or MR and target branch; use the repository default base only after reporting it.\n\n## Workflow\n1. Obtain the merge-base diff and list files examined.\n2. Compare changed blocks with nearby and existing code; distinguish deliberate repetition, generated code, and test fixtures.\n3. Report evidenced candidates with both locations, similarity, maintenance risk, and a proportionate suggestion.\n\n## Rules\n- Do not modify or remove code without explicit approval.\n- Do not label repeated literals alone as duplication without a maintenance consequence.\n- Report scope limits and skipped generated files.\n\n## Output\nReturn a Markdown table: candidate, locations, evidence, confidence, risk, suggested next step.`)},
{ id:'am-i-free', author:'Vinicius Nascimento', path:'../submitted-skills/Vinicius%20Nascimento/skills/am-i-free/SKILL.md', title:'Am I free?', status:'Good companion set', focus:'Calculate working time after lunch handling.', wins:['Exit-code handling makes the agents next action deterministic.','Friendly, human output matches the domain.'], improve:['Replace host-specific `$CLAUDE_SKILL_DIR` fallback paths with package-relative paths.','Document the data schema and timezone/DST assumptions in a reference.','Any `--default-lunch` write must ask for consent immediately before it occurs.'], extras:'Add tests for malformed JSON, overnight shifts, and a lunch end before lunch start.', improved:skill('am-i-free','Calculate elapsed work time from the Long Day Factory shift record. Use when the user asks whether they can leave or how much time remains.',`## Workflow\n1. Run \`python3 scripts/am_i_free.py\`.\n2. Interpret its documented exit code. Ask before any option that writes an assumed lunch break.\n3. Give the result, remaining time or release time, and a concise friendly message.\n\n## Rules\n- Treat malformed or missing state as a recovery question, not a calculation.\n- Read \`references/state.md\` for schema and timezone behavior.\n- Do not expose unrelated content from the local state file.\n\n## Output\nReport calculation status, remaining time or freedom, and any assumption made.`)},
{ id:'back-to-work', author:'Vinicius Nascimento', path:'../submitted-skills/Vinicius%20Nascimento/skills/back-to-work/SKILL.md', title:'Back to work', status:'Good companion set', focus:'Record return time after a lunch break.', wins:['Explains the relationship with the calculation skill.','Surfaces missing lunch/start state.'], improve:['Creating or changing a shift file is a mutation; state that the users “back to work” message is the authorization.','Use a package-relative script path.','Share state schema and error behavior with the other four companion skills.'], extras:'Add one script test for missing state and a reference shared by the suite.', improved:skill('back-to-work','Record the return time for a Long Day Factory lunch break. Use when the user says they have returned to work.',`## Workflow\n1. Confirm the message is an instruction to record the current return time.\n2. Run \`bash scripts/back.sh\`.\n3. Surface any missing shift or lunch state and explain the next recovery action.\n\n## Rules\n- This command changes local shift state; do not run it for a hypothetical question.\n- Use the shared state schema in \`references/state.md\`.\n\n## Output\nConfirm the recorded timestamp and any state warning with a light, respectful tone.`)},
{ id:'long-day-start', author:'Vinicius Nascimento', path:'../submitted-skills/Vinicius%20Nascimento/skills/long-day-start/SKILL.md', title:'Long day start', status:'Good companion set', focus:'Start a shift and reset prior lunch state.', wins:['Reset behavior is stated clearly.','The script provides a direct observable result.'], improve:['Highlight that it overwrites the prior shift state before execution.','Use package-relative script paths and shared state documentation.','Offer a “show current state” check before reset when a previous shift exists.'], extras:'Add an explicit confirmation branch for an existing incomplete shift.', improved:skill('long-day-start','Start a Long Day Factory shift by recording the current time and clearing lunch state. Use when the user explicitly says they have started their day.',`## Workflow\n1. Check whether an incomplete shift record exists.\n2. If it does, explain that starting a new shift replaces its lunch state and ask for confirmation.\n3. Run \`bash scripts/start.sh\` after explicit start authorization.\n\n## Rules\n- Do not reset a shift for a hypothetical or informational request.\n- Store and document times in timezone-aware ISO 8601 format.\n\n## Output\nConfirm the new start timestamp and whether a prior shift was replaced.`)},
{ id:'lunch-time', author:'Vinicius Nascimento', path:'../submitted-skills/Vinicius%20Nascimento/skills/lunch-time/SKILL.md', title:'Lunch time', status:'Good companion set', focus:'Record the beginning of a lunch break.', wins:['Narrow purpose and clear relationship to the suite.','Handles missing start state gracefully.'], improve:['Treat “going to lunch” as write authorization but keep queries non-mutating.','Use a package-relative script path.','Prevent overwriting an existing open lunch without confirmation.'], extras:'Share one state schema and add a test for duplicate lunch starts.', improved:skill('lunch-time','Record the start of a Long Day Factory lunch break. Use when the user explicitly says they are starting lunch.',`## Workflow\n1. Confirm the request records a lunch start now.\n2. Check for a started shift and an existing open lunch.\n3. If an open lunch exists, ask before replacing it; otherwise run \`bash scripts/lunch.sh\`.\n\n## Rules\n- This command changes local state; do not run it for a question about lunch time.\n- Use \`references/state.md\` for recovery rules.\n\n## Output\nConfirm the lunch timestamp and any missing or conflicting state.`)},
{ id:'backend-code-reviewer', author:'William Lino', path:'../submitted-skills/William%20Lino/skills/backend-code-reviewer/SKILL.md', title:'Backend code reviewer', status:'Restructure required', focus:'Review backend changes for architecture, reliability, performance, and security risks.', wins:['Ambitious and relevant issue categories.','CI reporting intent is useful.'], improve:['Missing frontmatter means it is not a valid, discoverable skill.','The referenced `dsa-reviewer` tool and curl-pipe-shell installation are unverified; never recommend executing them as written.','Split generic principles from language/framework-specific detection and define evidence thresholds to reduce false positives.'], extras:'Create `references/rules.md`, cite the actual scanner or use existing project tools, and add safe test fixtures before any CI integration.', improved:skill('backend-code-reviewer','Review a scoped backend change for evidenced security, reliability, data-access, and API-boundary risks. Use when reviewing a backend diff; do not install tools or modify CI unless the user asks.',`## Inputs\nA branch diff or changed backend paths and the projects declared tooling.\n\n## Workflow\n1. Identify runtime, framework, and existing checks from the repository.\n2. Review changed data access, async boundaries, error handling, API contracts, secrets, and resource limits.\n3. Report findings only when a concrete path and consequence are visible; label hypotheses separately.\n4. Run existing, approved checks and include their evidence.\n\n## Rules\n- Do not download or pipe remote installers into a shell.\n- Do not claim missing indexes, retries, or architectural violations without repository evidence.\n- Read \`references/rules.md\` for framework-specific checks.\n\n## Output\nReturn severity, location, evidence, impact, recommendation, and checks run.`)}
];
+41
View File
@@ -0,0 +1,41 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Friendly reviews and improved drafts for submitted Agent Skills." />
<title>Submitted Skills — Review Desk</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<main>
<header class="topbar"><a href="../" class="back">← field guide</a><span>SUBMITTED SKILLS / REVIEW DESK</span><a href="#catalog">16 submissions</a></header>
<section class="hero">
<p class="eyebrow">A friendly path from draft to dependable</p>
<h1>Every skill deserves<br><em>a clear job.</em></h1>
<p>Read the original, understand what already works, and compare a safer, leaner draft. Nothing here overwrites a submission; revisions live in their own review output.</p>
</section>
<section class="principles" aria-label="Review principles">
<article><b>01</b><strong>Discoverable</strong><span>A precise description tells an agent when to load the skill.</span></article>
<article><b>02</b><strong>Useful in context</strong><span>Core workflow stays short; conditional detail loads only when needed.</span></article>
<article><b>03</b><strong>Safe by design</strong><span>Commands, secrets, and shared systems have explicit boundaries.</span></article>
<article><b>04</b><strong>Proven in use</strong><span>Real prompts and observable checks turn a draft into a reliable tool.</span></article>
</section>
<section class="method">
<div><p class="eyebrow">How to use this desk</p><h2>Compare.<br><em>Then choose.</em></h2></div>
<ol><li>Select a submission, or open an author URL.</li><li>Read the gentle review before judging the draft.</li><li>Toggle Markdown to inspect either version.</li><li>Copy or download the version you want.</li></ol>
</section>
<section class="catalog" id="catalog">
<aside><p class="eyebrow">The catalog</p><label for="skill-filter">Find a skill</label><input id="skill-filter" type="search" placeholder="author, skill, topic" autocomplete="off"><p class="count" id="count"></p><div id="skill-list" role="listbox" aria-label="Submitted skills"></div></aside>
<article class="detail" id="detail" aria-live="polite"></article>
</section>
<section class="research">
<p class="eyebrow">Why these reviews look this way</p>
<p>The recommendations follow the open Agent Skills format: valid frontmatter for discovery, progressive disclosure for context economy, deterministic scripts for fragile repeated mechanics, and behavioral evaluation rather than a checklist of pretty headings.</p>
<div><a href="https://agentskills.io/specification" target="_blank" rel="noreferrer">Format specification ↗</a><a href="https://agentskills.io/skill-creation/best-practices" target="_blank" rel="noreferrer">Writing practices ↗</a><a href="https://agentskills.io/skill-creation/evaluating-skills" target="_blank" rel="noreferrer">Evaluation loop ↗</a><a href="https://agentskills.io/skill-creation/using-scripts" target="_blank" rel="noreferrer">Scripts guide ↗</a></div>
</section>
<footer>Share an author with <code>?author=Name</code>, or one review with <code>?author=Name&amp;skill=skill-id&amp;view=improved</code>. To add a submission later: drop a package under <code>submitted-skills/</code>, add a tailored entry in <code>skills-review/catalog.js</code>, then run <code>node scripts/build-skill-review.mjs</code>.</footer>
</main>
<script type="module" src="app.js"></script>
</body>
</html>
File diff suppressed because one or more lines are too long
+25
View File
@@ -0,0 +1,25 @@
---
name: skill-reviewer
description: Review an Agent Skill package and produce a kind, evidence-backed improvement brief. Use when assessing a SKILL.md, its trigger, instructions, scripts, references, safety, or evaluation readiness; do not rewrite the package unless asked.
---
# Skill reviewer
Review the submitted package before proposing changes. Preserve the author's intent: this is a constructive assessment, not a replacement of their domain expertise.
## Review flow
1. Read `SKILL.md` and list bundled files. Check frontmatter validity, package-name alignment, and whether the description says both what the skill does and when it applies.
2. Identify the narrow job, the expected inputs, safe boundaries, a default workflow, and observable output. Mark any claim you cannot verify as a question, not a defect.
3. Recommend only additions that change execution: a small RULES section for real invariants, a script for repeated fragile work, a reference for conditional detail, or eval cases for behavior that matters.
4. Flag secrets, destructive actions, network calls, and unclear approval boundaries prominently. Never copy credentials into review artifacts.
5. Return a friendly brief with: what already works, highest-value improvements, suggested package layout, and a small set of realistic test prompts.
## Quality bar
- Prefer precise activation language over broad phrases such as "use for code."
- Keep the main instructions lean; send conditional or lengthy material to `references/` and explain exactly when to read it.
- Favor evidence and defaults over generic rules or tool menus.
- Recommend scripts only when they remove repeated, error-prone mechanics; document prerequisites and use relative paths.
Read [the review rubric](references/review-rubric.md) when scoring a package.
@@ -0,0 +1,7 @@
# Review rubric
Assess six dimensions: discoverability, scope, procedure, safety, resources, and proof.
For each finding, state the observed evidence, the practical consequence, and the smallest helpful change. Do not call missing files a problem unless the workflow genuinely needs them. A strong review explains why the recommendation belongs in the skill rather than in general agent behavior.
Test prompts should include one normal request and one boundary case. Assertions should be observable, such as valid JSON, an explicit approval request before mutation, or a report containing file locations.
+19
View File
@@ -0,0 +1,19 @@
---
name: skill-rewriter
description: Rewrite an existing Agent Skill into a concise, safer, and more discoverable package while preserving its intended capability. Use after a skill review or when the user asks to improve a SKILL.md; do not alter original submissions in place without explicit approval.
---
# Skill rewriter
Create a separate revised package so the author can compare it with the original. Retain domain-specific facts that are supported by the source; replace generic filler with decisions the agent would otherwise miss.
## Rewrite flow
1. Read the original package and any review brief. Keep its intended job and remove only unsupported assumptions, unsafe commands, or instructions that conflict with the requested boundary.
2. Write valid frontmatter: a lowercase hyphenated name matching the folder and a description that states capability plus trigger terms.
3. Use a short, friendly structure: Purpose, When to use, Inputs, Workflow, Rules, Output, and Verification. Omit headings that add no decision-making value.
4. Move conditional detail to `references/`; add a script only for deterministic repeated work and name its prerequisites. Use paths relative to the skill root.
5. Add concrete safety gates for mutation, credentials, and external systems. Never preserve a secret in the rewritten package.
6. Validate the new package and give the author an end-to-end explanation of the changes and one next evaluation step.
Read [the rewrite checklist](references/rewrite-checklist.md) for final checks.
@@ -0,0 +1,10 @@
# Rewrite checklist
- `name` is lowercase, hyphenated, and matches the folder.
- `description` says what the skill does and when to use it.
- The default workflow has clear inputs and a concrete result.
- Important limits have a reason; rules are not generic boilerplate.
- Bundled resources are linked from `SKILL.md` and loaded only when needed.
- Commands use relative paths and document prerequisites.
- Mutations require an explicit user approval at the moment they occur.
- The package has one normal and one boundary-case evaluation prompt.
@@ -0,0 +1,112 @@
---
name: code-style-review
description: Run automated linters, Checkstyle, and formatting scripts to validate and fix code style without consuming unnecessary LLM tokens.
---
# Code Style & Automated Linting
Use this skill after modifying code files to trigger local static analysis tools and fix formatting issues automatically.
## When to use
- After completing any backend (Java) or frontend changes.
- Before running MR self-reviews or committing code.
## Core rules
### Indentation & formatting
- TypeScript, JavaScript, JSX, JSON, HTML, CSS, Less: 2 spaces per indentation level.
- Java, XML: 4 spaces per indentation level.
- Do not use hard tabs unless the existing file already uses them consistently.
- Remove trailing whitespace from all lines.
- Ensure every file ends with exactly one empty newline (POSIX standard).
- Keep line length reasonable; break long lines rather than letting them scroll far beyond 120 characters.
- Maintain consistent brace style with the surrounding file.
### Code hygiene
- Remove unused imports, variables, functions and types.
- Remove dead code, commented-out experiments and placeholder snippets.
- Delete leftover debugging statements: `console.log`, `console.warn`, `console.error`, `System.out.println`, `printStackTrace`, etc.
- Do not leave `TODO` or `FIXME` comments unless explicitly approved and tracked.
- Keep imports organized and free of duplicates.
- Ensure naming follows the conventions already used in the file/module.
## Execution steps
### 1. Backend verification (Java / Maven)
Run the automated style check in the `backend` directory:
```bash
cd backend
mvn checkstyle:check
```
If violations are found, fix them or run the auto-formatter if configured:
```bash
cd backend
mvn spotless:apply
```
Then rerun:
```bash
cd backend
mvn checkstyle:check
```
### 2. Frontend verification (TypeScript / JavaScript)
Run the frontend linter and formatter:
```bash
cd frontend
npx eslint src/ --ext .ts,.tsx,.js,.jsx
npx prettier --check src/
```
If formatting issues are found, apply Prettier:
```bash
cd frontend
npx prettier --write src/
```
### 3. Final check
- [ ] Backend `mvn checkstyle:check` passes.
- [ ] Frontend ESLint reports no errors.
- [ ] Frontend Prettier reports no formatting differences.
- [ ] No unintended files were reformatted.
- [ ] No leftover debugging statements remain.
## Output format
Return findings as:
```text
Tool / Severity / File / Line / Message / Recommendation
```
Severity levels: `ERROR`, `WARNING`, `INFO`.
If all checks pass, say explicitly:
```text
All automated style checks passed.
```
Example summary block:
```markdown
## Code Style & Automated Linting
- Backend Checkstyle: PASS / FAIL — reason
- Frontend ESLint: PASS / FAIL — reason
- Frontend Prettier: PASS / FAIL — reason
```
If any check fails, apply the recommended fix and rerun the tool before finishing unless the user asks to skip.
@@ -0,0 +1,86 @@
---
name: sql-injection-audit
description: Check repository code for SQL injection vulnerabilities. Use when creating, modifying, reviewing, or debugging code that builds or executes SQL queries.
SQL Injection Audit
---
# SQL Injection analysis
Use this skill when working with code that interacts with relational databases or constructs SQL queries.
## Core Rules
- Treat all external/user-controlled input as untrusted.
- Never concatenate or interpolate untrusted input directly into SQL.
- Prefer parameterized queries or prepared statements.
- Use ORM/query-builder parameterization when available.
- Do not rely on input sanitization or escaping as the primary defense.
- Review raw SQL and ORM escape-hatch APIs carefully.
- Validate dynamic SQL identifiers such as table names and column names with strict allowlists.
- Consider second-order SQL injection when user-controlled data is stored and later used in SQL.
- Do not consider tests passing as proof that SQL injection is impossible.
## Review Workflow
1. Identify SQL execution points:
- raw SQL;
- database driver queries;
- ORM raw queries;
- query builders;
- stored procedures;
- dynamically generated SQL.
2. Trace untrusted input into SQL:
- HTTP parameters;
- request bodies;
- headers;
- cookies;
- GraphQL inputs;
- CLI arguments;
- external API data;
- stored user-controlled data.
3. Look for dangerous patterns:
- string concatenation;
- template literals;
- dynamic WHERE clauses;
- dynamic ORDER BY;
- dynamic table/column names;
- raw SQL fragments;
- unsafe ORM APIs.
4. Verify the fix:
- confirm values are passed as SQL parameters;
- confirm dynamic identifiers use an allowlist;
- review relevant tests;
- run existing security/static-analysis tools when available.
5. Report findings with:
- severity;
- file and line;
- source of untrusted input;
- SQL sink;
- data flow;
- impact;
- recommended fix.
- Secure Pattern
## Completion Criteria
Before completing the task:
- Relevant SQL queries were reviewed.
- Untrusted input flows were checked.
- Raw SQL and ORM escape hatches were reviewed.
- Parameterization was verified.
- Dynamic identifiers were checked.
- Relevant tests were reviewed or run.
- Any SQL injection risk is explicitly reported.
If the requested change introduces SQL injection, stop and explain the vulnerability and recommend a parameterized or otherwise safe implementation.
@@ -0,0 +1,104 @@
# Confectionery Skills Hub
A set of skills (*tool definitions*) for recipe management and order processing in a sweet shop / confectionery.
---
## 1. Skill: `create_recipe`
Registers a new dessert recipe in the sweet shop's catalog.
### When to use
* The user wants to register a new recipe, cake, candy, or preparation.
* The user provides a list of ingredients and yield weight for registration.
### Parameter Schema
| Field | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `recipe_name` | `string` | Yes | Official name of the recipe (e.g., `"Carrot Cake with Brigadeiro"`). |
| `type` | `string` (enum) | Yes | Category: `"cake"`, `"candy"`, `"ice_cream"`, `"pie"`, `"other"`. |
| `yield_kg` | `number` | Yes | Estimated final yield in kg (e.g., `1.8`). |
| `ingredients` | `string[]` | Yes | List of ingredients with approximate quantities. |
| `description` | `string` | No | Brief preparation method or sensory notes. |
### Sample Input (Tool Call)
```json
{
"recipe_name": "Ninho Volcano Cake",
"type": "cake",
"yield_kg": 2.1,
"ingredients": [
"4 eggs",
"2 cups all-purpose flour",
"1 cup powdered milk",
"1 can sweetened condensed milk",
"200ml heavy cream"
],
"description": "Fluffy cake with generous creamy filling in the center."
}
```
## 2. Skill: `search_recipe`
Searches the catalog to list recipes by name or category.
### When to use
* The user asks whether a specific dessert is on the menu.
* The user wants to see ingredients or view items belonging to a specific category (e.g., "what pies do we have?").
### Parameter Schema
| Field | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `search_term` | `string` | No | Keyword or partial name of the dessert (e.g., `"brigadeiro"`). |
| `type` | `string` (enum) | No | Category filter: `"cake"`, `"candy"`, `"ice_cream"`, `"pie"`, `"other"`. |
### Sample Input (Tool Call)
```json
{
"search_term": "carrot",
"type": "cake"
}
```
## 3. Skill: `create_order`
Registers a new custom order or counter sale in the sweet shop.
### When to use
* The customer or attendant requests to complete an order.
* Items to purchase, customer details, and delivery information are provided.
### Parameter Schema
| Field | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `customer_name` | `string` | Yes | Full name of the customer. |
| `delivery_address` | `string` | Yes | Shipping address or `"Store Pickup"`. |
| `items` | `object[]` | Yes | List containing the purchased items. |
| `items[].item_name` | `string` | Yes | Name of the product. |
| `items[].quantity` | `integer` | Yes | Quantity of units or portions. |
| `items[].unit_price` | `number` | Yes | Unit price in local currency (BRL). |
| `discount` | `number` | No | Flat discount amount applied in local currency (BRL). Default: `0`. |
### Sample Input (Tool Call)
```json
{
"customer_name": "Fernanda Lima",
"delivery_address": "Av. Paulista, 1000 - Apt 42",
"items": [
{
"item_name": "100-Pack of Gourmet Brigadeiros",
"quantity": 1,
"unit_price": 120.00
},
{
"item_name": "Whole Dutch Pie",
"quantity": 1,
"unit_price": 85.00
}
],
"discount": 15.00
}
```
@@ -0,0 +1,42 @@
---
name: angular-access-modifiers-francisco-rangel
description: Enforces explicit TypeScript access modifiers (public/protected/private) on every class member of an Angular component, directive, or pipe based on usage.
---
# Angular Access Modifiers
Every field, getter/setter, and method on an Angular class must have an **explicit** TypeScript access modifier. Never leave members implicit.
## Visibility Rules
| Used in HTML template? | Used only inside TS class? | External access (Parent, Test, Service)? | Access Modifier |
| :--- | :--- | :--- | :--- |
| **Yes** | — | — | `protected` |
| **No** | **Yes** | **No** | `private` |
| **No** | — | **Yes** | `public` |
---
## Instructions
1. **`protected`**: Use for all properties, signals, getters/setters, and methods accessed directly inside the template (`.html` or inline `template`).
2. **`private`**: Use for internal logic, helper methods, state variables, or subscriptions that are never accessed outside this single file.
3. **`public`**: Use ONLY for `@Input()`, `@Output()`, component inputs/outputs created via functions (`input()`, `output()`), public API methods called by parents/tests, or Angular lifecycle hooks (`ngOnInit`, `ngOnDestroy`, etc.).
4. **Never leave any member without an explicit modifier.**
## Examples
### ❌ Incorrect (Implicit or misscoped)
```typescript
@Component({ ... })
export class UserProfileComponent {
userName = signal('John'); // Implicit public (avoid)
ngOnInit() { // Implicit public
this.fetchData();
}
fetchData() { // Implicit public
// ...
}
}
@@ -0,0 +1,32 @@
---
name: codebase-map
description: "Maintains FEATURE_MAP.md, a one-line-per-feature index of where things live in the codebase. Read it before searching for code to change so you can skip re-exploring; update it after a change adds, moves, or renames a feature's location."
---
# Codebase Map
`FEATURE_MAP.md` at the repo root caches the answer to one question: where does feature X live? A stale entry is worse than no entry — it sends you confidently to the wrong place instead of triggering a real search. Every rule below exists to keep the map cheap to build and safe to trust.
## Before searching for code to change
1. Read `FEATURE_MAP.md` if it exists.
2. Feature listed? Confirm the exact path in that entry still exists — a quick `ls`/glob, not a full read. If it does, go straight there; no exploratory search needed. If it doesn't, the entry is stale: delete it and fall through to step 3.
3. Not listed (or no map yet): search normally — grep for the concrete symbol, route, or keyword — then add or fix the entry once you find it.
## After implementing a change
Update the matching line, as part of the same change, whenever the change adds a feature or changes the path an entry points to (moved, renamed, split up). Edits that leave that path untouched need no update, no matter how much the file's contents changed.
## Format
One line per feature/flow. The path must be the single most specific real file or directory that answers "where do I start reading" — that's what step 2 checks, so it's what has to stay current. Don't split path and entry-point across separate fields: an unchecked field goes stale silently.
- Payment flow — `src/domain/payment/PaymentProcessor.ts` (`process()`)
- Auth / login — `src/auth/session.ts` (`issueSession()`)
- Email notifications — `src/messaging/email/` (multiple files, no single entry point)
Group under `##` headers (Domain, API, Frontend, Infra) only once the flat list gets hard to scan.
## Bootstrapping
No map yet? Build it once: skim top-level directories and manifests, list the major features/flows, one line each. A handful of entries covering the main flows beats an exhaustive file — let step 3 above fill in the rest lazily, as you touch each area.
+515
View File
@@ -0,0 +1,515 @@
---
name: angular-accessibility
description: Enforce and improve accessibility (a11y) in Angular applications following WCAG 2.2 AA, ARIA best practices, semantic HTML, and Angular-specific patterns.
---
# Angular Accessibility Skill
## Purpose
This skill helps build and review Angular applications that are accessible by default. It prioritizes semantic HTML, keyboard navigation, screen reader compatibility, color contrast, focus management, and Angular CDK accessibility utilities.
Target standard: **WCAG 2.2 Level AA**
## When to Use
Activate this skill whenever the task involves:
- Creating Angular components
- Reviewing templates for accessibility
- Refactoring UI components
- Building forms
- Navigation menus
- Dialogs and modals
- Tables
- Custom controls
- Angular Material components
- Accessibility audits
- Fixing Lighthouse or axe-core accessibility issues
---
# Accessibility Principles
Always follow this priority order:
1. Semantic HTML
2. Native browser behavior
3. Angular accessibility utilities
4. ARIA only when necessary
**Rule:** Never use ARIA to replace native HTML functionality.
Example:
Good:
```html
<button type="button">Save</button>
```
Avoid:
```html
<div role="button">Save</div>
```
---
# Angular Template Rules
## Buttons
Always:
- use `<button>`
- specify `type`
- provide accessible text
Good:
```html
<button type="submit">Submit</button>
```
Icon button:
```html
<button type="button" aria-label="Close dialog">
<mat-icon>close</mat-icon>
</button>
```
---
## Links
Use `<a>` only for navigation.
Good:
```html
<a routerLink="/dashboard">Dashboard</a>
```
Avoid:
```html
<a (click)="save()">Save</a>
```
Use a button instead.
---
## Images
Decorative:
```html
<img src="divider.svg" alt="">
```
Informative:
```html
<img src="profile.jpg" alt="Jane Doe smiling">
```
Avoid generic alt text like "image" or "photo."
---
# Forms
## Labels
Every input needs a label.
Good:
```html
<label for="email">Email</label>
<input id="email" type="email">
```
Angular Material:
```html
<mat-form-field>
<mat-label>Email</mat-label>
<input matInput type="email">
</mat-form-field>
```
---
## Error Messages
Requirements:
- visible
- descriptive
- associated with the input
Example:
```html
<input
id="email"
aria-describedby="email-error">
<div id="email-error">
Enter a valid email address.
</div>
```
Avoid relying on color alone.
---
## Required Fields
Use both:
```html
<input required aria-required="true">
```
---
# Keyboard Accessibility
Every interactive element must be usable with:
- Tab
- Shift+Tab
- Enter
- Space
- Escape (when applicable)
- Arrow keys (where expected)
Never trap keyboard focus.
---
# Focus Management
Use Angular CDK when possible.
Example:
```typescript
constructor(private focusMonitor: FocusMonitor) {}
```
For dialogs:
- move focus into dialog
- trap focus
- restore focus on close
Angular Material already provides this behavior.
---
# Angular CDK Accessibility
Prefer Angular CDK utilities.
Useful services:
- FocusMonitor
- LiveAnnouncer
- InteractivityChecker
- FocusTrapFactory
Example:
```typescript
this.liveAnnouncer.announce('Settings saved');
```
Use for:
- success messages
- validation updates
- dynamic content
---
# ARIA Usage
Use ARIA only when native HTML cannot express the behavior.
Common attributes:
| Attribute | Use |
|-----------|-----|
| aria-label | Icon buttons |
| aria-labelledby | Existing visible label |
| aria-describedby | Helper/error text |
| aria-expanded | Expandable controls |
| aria-controls | Controlled region |
| aria-live | Dynamic announcements |
| aria-current | Current navigation item |
Avoid redundant ARIA.
Bad:
```html
<button role="button">
```
---
# Navigation
Provide a skip link.
Example:
```html
<a href="#main" class="skip-link">
Skip to main content
</a>
```
Use landmarks:
```html
<header>
<nav>
<main id="main">
<footer>
```
---
# Tables
Use proper table structure.
Good:
```html
<table>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Role</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>Admin</td>
</tr>
</tbody>
</table>
```
Avoid tables for layout.
---
# Dialogs
Requirements:
- focus trap
- Escape closes dialog
- initial focus
- restore focus afterward
Angular Material Dialog already supports most of these.
Add:
```html
<h2 mat-dialog-title>
```
for proper dialog labeling.
---
# Custom Components
When creating custom controls:
Implement:
- keyboard interaction
- focus visibility
- accessible name
- appropriate ARIA state
Example checklist:
- [ ] Tab reachable
- [ ] Enter works
- [ ] Space works
- [ ] Focus visible
- [ ] Screen reader announces purpose
---
# Color and Contrast
Minimum ratios:
| Text | Ratio |
|------|-------|
| Normal | 4.5:1 |
| Large | 3:1 |
Never communicate information using color alone.
Instead of:
- Red = error
Use:
- icon
- text
- color
---
# Focus Indicators
Never remove focus outlines unless replacing them.
Good:
```css
:focus-visible {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
```
Avoid:
```css
outline: none;
```
---
# Motion
Respect reduced motion.
Example:
```css
@media (prefers-reduced-motion: reduce) {
* {
animation: none;
transition: none;
}
}
```
---
# Angular Material Guidance
Prefer built-in accessible components.
Good choices:
- MatButton
- MatDialog
- MatMenu
- MatCheckbox
- MatRadio
- MatSelect
- MatSnackBar
- MatTabs
Verify:
- labels
- keyboard support
- announcements
---
# Testing Checklist
Before completing any accessibility task:
## Keyboard
- [ ] Everything reachable with Tab
- [ ] No keyboard traps
- [ ] Enter works
- [ ] Space works
- [ ] Escape works where appropriate
## Screen Reader
- [ ] Controls have accessible names
- [ ] Form fields have labels
- [ ] Errors are announced
- [ ] Dynamic updates are announced
## Visual
- [ ] Contrast passes WCAG
- [ ] Focus visible
- [ ] No color-only communication
- [ ] Text scales properly
---
# Automated Testing
Recommend these tools:
## Angular ESLint
Enable accessibility rules.
## axe-core
Use for automated audits.
Example:
- axe DevTools
- Cypress + axe
- Playwright + axe
## Lighthouse
Run accessibility audits regularly.
Treat Lighthouse as a guide rather than the only authority.
---
# Code Review Rules
Whenever reviewing Angular code:
1. Replace non-semantic elements with semantic HTML.
2. Add missing labels.
3. Improve keyboard support.
4. Remove unnecessary ARIA.
5. Fix focus management.
6. Ensure dynamic updates are announced.
7. Verify Angular Material accessibility.
8. Confirm WCAG 2.2 AA compliance.
Always explain:
- why the issue affects accessibility
- the WCAG principle involved
- the preferred Angular solution
- the corrected code
@@ -0,0 +1,515 @@
---
name: angular-accessibility
description: Enforce and improve accessibility (a11y) in Angular applications following WCAG 2.2 AA, ARIA best practices, semantic HTML, and Angular-specific patterns.
---
# Angular Accessibility Skill
## Purpose
This skill helps build and review Angular applications that are accessible by default. It prioritizes semantic HTML, keyboard navigation, screen reader compatibility, color contrast, focus management, and Angular CDK accessibility utilities.
Target standard: **WCAG 2.2 Level AA**
## When to Use
Activate this skill whenever the task involves:
- Creating Angular components
- Reviewing templates for accessibility
- Refactoring UI components
- Building forms
- Navigation menus
- Dialogs and modals
- Tables
- Custom controls
- Angular Material components
- Accessibility audits
- Fixing Lighthouse or axe-core accessibility issues
---
# Accessibility Principles
Always follow this priority order:
1. Semantic HTML
2. Native browser behavior
3. Angular accessibility utilities
4. ARIA only when necessary
**Rule:** Never use ARIA to replace native HTML functionality.
Example:
Good:
```html
<button type="button">Save</button>
```
Avoid:
```html
<div role="button">Save</div>
```
---
# Angular Template Rules
## Buttons
Always:
- use `<button>`
- specify `type`
- provide accessible text
Good:
```html
<button type="submit">Submit</button>
```
Icon button:
```html
<button type="button" aria-label="Close dialog">
<mat-icon>close</mat-icon>
</button>
```
---
## Links
Use `<a>` only for navigation.
Good:
```html
<a routerLink="/dashboard">Dashboard</a>
```
Avoid:
```html
<a (click)="save()">Save</a>
```
Use a button instead.
---
## Images
Decorative:
```html
<img src="divider.svg" alt="">
```
Informative:
```html
<img src="profile.jpg" alt="Jane Doe smiling">
```
Avoid generic alt text like "image" or "photo."
---
# Forms
## Labels
Every input needs a label.
Good:
```html
<label for="email">Email</label>
<input id="email" type="email">
```
Angular Material:
```html
<mat-form-field>
<mat-label>Email</mat-label>
<input matInput type="email">
</mat-form-field>
```
---
## Error Messages
Requirements:
- visible
- descriptive
- associated with the input
Example:
```html
<input
id="email"
aria-describedby="email-error">
<div id="email-error">
Enter a valid email address.
</div>
```
Avoid relying on color alone.
---
## Required Fields
Use both:
```html
<input required aria-required="true">
```
---
# Keyboard Accessibility
Every interactive element must be usable with:
- Tab
- Shift+Tab
- Enter
- Space
- Escape (when applicable)
- Arrow keys (where expected)
Never trap keyboard focus.
---
# Focus Management
Use Angular CDK when possible.
Example:
```typescript
constructor(private focusMonitor: FocusMonitor) {}
```
For dialogs:
- move focus into dialog
- trap focus
- restore focus on close
Angular Material already provides this behavior.
---
# Angular CDK Accessibility
Prefer Angular CDK utilities.
Useful services:
- FocusMonitor
- LiveAnnouncer
- InteractivityChecker
- FocusTrapFactory
Example:
```typescript
this.liveAnnouncer.announce('Settings saved');
```
Use for:
- success messages
- validation updates
- dynamic content
---
# ARIA Usage
Use ARIA only when native HTML cannot express the behavior.
Common attributes:
| Attribute | Use |
|-----------|-----|
| aria-label | Icon buttons |
| aria-labelledby | Existing visible label |
| aria-describedby | Helper/error text |
| aria-expanded | Expandable controls |
| aria-controls | Controlled region |
| aria-live | Dynamic announcements |
| aria-current | Current navigation item |
Avoid redundant ARIA.
Bad:
```html
<button role="button">
```
---
# Navigation
Provide a skip link.
Example:
```html
<a href="#main" class="skip-link">
Skip to main content
</a>
```
Use landmarks:
```html
<header>
<nav>
<main id="main">
<footer>
```
---
# Tables
Use proper table structure.
Good:
```html
<table>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Role</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>Admin</td>
</tr>
</tbody>
</table>
```
Avoid tables for layout.
---
# Dialogs
Requirements:
- focus trap
- Escape closes dialog
- initial focus
- restore focus afterward
Angular Material Dialog already supports most of these.
Add:
```html
<h2 mat-dialog-title>
```
for proper dialog labeling.
---
# Custom Components
When creating custom controls:
Implement:
- keyboard interaction
- focus visibility
- accessible name
- appropriate ARIA state
Example checklist:
- [ ] Tab reachable
- [ ] Enter works
- [ ] Space works
- [ ] Focus visible
- [ ] Screen reader announces purpose
---
# Color and Contrast
Minimum ratios:
| Text | Ratio |
|------|-------|
| Normal | 4.5:1 |
| Large | 3:1 |
Never communicate information using color alone.
Instead of:
- Red = error
Use:
- icon
- text
- color
---
# Focus Indicators
Never remove focus outlines unless replacing them.
Good:
```css
:focus-visible {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
```
Avoid:
```css
outline: none;
```
---
# Motion
Respect reduced motion.
Example:
```css
@media (prefers-reduced-motion: reduce) {
* {
animation: none;
transition: none;
}
}
```
---
# Angular Material Guidance
Prefer built-in accessible components.
Good choices:
- MatButton
- MatDialog
- MatMenu
- MatCheckbox
- MatRadio
- MatSelect
- MatSnackBar
- MatTabs
Verify:
- labels
- keyboard support
- announcements
---
# Testing Checklist
Before completing any accessibility task:
## Keyboard
- [ ] Everything reachable with Tab
- [ ] No keyboard traps
- [ ] Enter works
- [ ] Space works
- [ ] Escape works where appropriate
## Screen Reader
- [ ] Controls have accessible names
- [ ] Form fields have labels
- [ ] Errors are announced
- [ ] Dynamic updates are announced
## Visual
- [ ] Contrast passes WCAG
- [ ] Focus visible
- [ ] No color-only communication
- [ ] Text scales properly
---
# Automated Testing
Recommend these tools:
## Angular ESLint
Enable accessibility rules.
## axe-core
Use for automated audits.
Example:
- axe DevTools
- Cypress + axe
- Playwright + axe
## Lighthouse
Run accessibility audits regularly.
Treat Lighthouse as a guide rather than the only authority.
---
# Code Review Rules
Whenever reviewing Angular code:
1. Replace non-semantic elements with semantic HTML.
2. Add missing labels.
3. Improve keyboard support.
4. Remove unnecessary ARIA.
5. Fix focus management.
6. Ensure dynamic updates are announced.
7. Verify Angular Material accessibility.
8. Confirm WCAG 2.2 AA compliance.
Always explain:
- why the issue affects accessibility
- the WCAG principle involved
- the preferred Angular solution
- the corrected code
@@ -0,0 +1,36 @@
---
name: copy-quote-info-to-payload
description: Fill a quote command payload from quote data. Use when the user asks to "copy quote info to payload", "copy quote data into the command", "fill the quote command from the quote", or provides a quote-data JSON plus a quote-command skeleton JSON and wants the command populated. Takes info from the source quote and fills it into the command skeleton, copying all quote items across unless the user asks for changes.
---
# Copy quote info to payload
Populate a **quote command** (target skeleton) with data taken from **quote data** (source), and return the filled command as valid JSON.
## Inputs
The user provides two JSON documents (as files, paths, or pasted text):
1. **Quote data** — the source. Has a top-level `quote` object and an `items` array. Items have a `type` such as `productItem`, `locationItem`, `alertItem`.
2. **Quote command skeleton** — the target to fill. Shape varies widely; it may contain `businessCommand`, `id`, `items`, `batchCommands`, `quoteCmd`, placeholders like `{{quoteId}}`, etc.
If either document is missing or ambiguous (e.g. two files given but it's unclear which is source vs. target), ask which is which before proceeding. The source is the one with the `quote` object + populated `items`; the target is the one with `businessCommand` / placeholders / empty item lists.
## Procedure
1. Parse both JSON documents.
2. Start from the **command skeleton** and preserve its exact structure, key order, and any keys the source has no data for (leave them as-is).
3. Fill fields **only** from the source quote. Do not invent values. See `reference.md` for the field-mapping table.
4. Replace placeholders (e.g. `{{quoteId}}`, wherever they appear including inside `batchCommands`) with the matching source value (`{{quoteId}}``quote.id`).
5. **Copy quote items faithfully.** Wherever the skeleton expects items, copy the corresponding items from the source across with **no changes** — same ids, order, and any other fields the skeleton's item shape uses — unless the user explicitly requests a change. Apply only the changes the user names; leave everything else untouched. See `reference.md` for how to pick which items go where (e.g. `productItem`s into a `product_items_modify` block).
6. If a field the skeleton needs isn't present in the source, leave the skeleton's original value/placeholder and note it in your summary rather than guessing.
7. Output the completed command as a single valid JSON document. Then give a short summary of what was mapped, which items were copied, and anything left unfilled.
## Rules
- Never fabricate data. Every filled value must come from the source quote (or from an explicit user instruction).
- Copy items as-is by default; only change what the user specifies.
- Preserve the skeleton's overall shape — the command format can vary greatly, so adapt to whatever keys it has instead of assuming a fixed template.
- Keep JSON valid and, where the skeleton had a style, match its formatting.
See `reference.md` for the detailed field mapping, item-selection rules, and a full worked example.
@@ -0,0 +1,123 @@
# Reference: Copy quote info to payload
This file holds the detailed mapping rules and a worked example. The main procedure is in `SKILL.md`.
## Source structure (quote data)
```
{
"quote": {
"id": "...", // the quote id
"customerId": "...",
"customerCategoryId": "...",
"distributionChannelId": "...",
"attributes": { ... },
"orderItemIds": [ ... ], // root product-item ids
"opportunityId": "...",
...
},
"items": [
{ "id": "...", "type": "productItem", ... },
{ "id": "...", "type": "locationItem", ... },
{ "id": "...", "type": "alertItem", ... },
...
]
}
```
## Target structure (quote command skeleton)
The command shape **varies greatly**. Adapt to whatever keys exist. A common example:
```
{
"businessCommand": "quote_init",
"businessCommandAttributes": { ... },
"id": "{{quoteId}}",
"items": [],
"batchCommands": [
{
"businessCommand": "product_items_modify",
"businessCommandAttributes": { "date": "..." },
"id": "{{quoteId}}",
"items": [ { "id": "...", "type": "productItem" }, ... ]
}
],
"quoteCmd": {
"attributes": {},
"customerId": "...",
"customerCategoryId": "...",
"distributionChannelId": "..."
}
}
```
## Field mapping (source → target)
Apply a mapping only when the target has a slot for it. Match by key name and meaning.
| Target field (wherever it appears) | Source value |
| ------------------------------------------------- | ----------------------------------------- |
| `{{quoteId}}` placeholder, top-level `id`, batch `id` | `quote.id` |
| `quoteCmd.customerId` / any `customerId` | `quote.customerId` |
| `quoteCmd.customerCategoryId` / `customerCategoryId` | `quote.customerCategoryId` |
| `quoteCmd.distributionChannelId` / `distributionChannelId` | `quote.distributionChannelId` |
| `quoteCmd.attributes` (when empty and desired) | `quote.attributes` (only if user wants it)|
| `opportunityId` | `quote.opportunityId` |
| `marketId` on items | item's `marketId` from source |
Notes:
- If the skeleton already has a hardcoded value (e.g. a sample `customerId`) and it differs from the source, replace it with the source value — the point is to reflect the source quote. Mention the replacement in the summary.
- If the skeleton has a `date`/timestamp the source doesn't provide (e.g. `businessCommandAttributes.date`), leave the skeleton's value as-is unless the user gives one.
- `quoteCmd.attributes` is often intentionally `{}`. Do **not** dump `quote.attributes` into it unless the user asks — attribute keys in the command context may differ.
## Item-selection rules
- **Product items:** items in the source with `"type": "productItem"`. These are the ones that typically go into a `product_items_modify` (or similar) block's `items` array as `{ "id": <sourceId>, "type": "productItem" }`.
- **Location items** (`"type": "locationItem"`) and **alert items** (`"type": "alertItem"`) are usually *not* copied into a product-items block. Copy them only where the skeleton has a matching slot for that type.
- **Copy all matching items** from the source into the target's item slot, preserving order and ids, using the field shape the skeleton's item entries use (often just `id` + `type`).
- Copy everything **unchanged** unless the user specifies a change (e.g. "set quantity to 2 on the Fibre item", "drop the DISCONNECT item", "change action to ADD"). Apply only what they name.
- Root vs. child products: `quote.orderItemIds` lists the root product ids. If the skeleton only wants roots, use those; if it wants all product items, use every `productItem`. When unclear, default to all `productItem`s and note it.
## Worked example
**Source (quote data):** `quote.id = d968445a-f813-4be1-899d-e06accb6473b`, `customerId = slotest`, `customerCategoryId = 0ded2167-c41b-4f58-9941-dbd247b1985d`, `distributionChannelId = CPMS`. Product items in `items`:
`9b4be199-4b65-4ec0-b54b-d2f61366c9ed`, `81a3fb42-31a5-4ea8-a668-8973e57aa2f9`, `c6a7aacd-8d3b-4c44-8680-829b15be5c06`, `fcd59bff-4339-4fea-8168-bb8598e98085` (plus location and alert items, which are not product items).
**Skeleton:** the `quote_init` + `product_items_modify` command shown above.
**Filled result:**
```json
{
"businessCommand": "quote_init",
"businessCommandAttributes": {
"itemTypesScope": []
},
"id": "d968445a-f813-4be1-899d-e06accb6473b",
"items": [],
"batchCommands": [
{
"businessCommand": "product_items_modify",
"businessCommandAttributes": {
"date": "2026-08-28T10:00:00.000-03:00"
},
"id": "d968445a-f813-4be1-899d-e06accb6473b",
"items": [
{ "id": "9b4be199-4b65-4ec0-b54b-d2f61366c9ed", "type": "productItem" },
{ "id": "81a3fb42-31a5-4ea8-a668-8973e57aa2f9", "type": "productItem" },
{ "id": "c6a7aacd-8d3b-4c44-8680-829b15be5c06", "type": "productItem" },
{ "id": "fcd59bff-4339-4fea-8168-bb8598e98085", "type": "productItem" }
]
}
],
"quoteCmd": {
"attributes": {},
"customerId": "slotest",
"customerCategoryId": "0ded2167-c41b-4f58-9941-dbd247b1985d",
"distributionChannelId": "CPMS"
}
}
```
Summary in this example: filled `{{quoteId}}` (both occurrences) from `quote.id`; set `customerId`, `customerCategoryId`, `distributionChannelId` from the source (replacing the skeleton's sample values); copied all 4 `productItem`s into the `product_items_modify` block unchanged; left `businessCommandAttributes.date` as-is (not present in source); kept `quoteCmd.attributes` empty (not requested).
@@ -0,0 +1,104 @@
---
name: generated-code-explanation
description: Explain code that is being introduced or changed in the Netcracker Telekom demo project. Use when summarizing implementation intent, design rationale, trade-offs, or the reasoning behind a chosen approach.
---
# Generated Code Explanation
Use this skill whenever the task requires explaining code that was (or will be) generated, modified, or reviewed. The goal is to make the **what** and the **why** explicit for readers, reviewers, and future maintainers.
## When to Use This Skill
- After implementing a feature or fix and the user asks for an explanation.
- When writing commit messages, PR descriptions, inline comments, or documentation.
- When reviewing code and summarizing what it does and why it was done this way.
- When onboarding someone to a module, component, or algorithm.
- When the user explicitly asks: “explain what this code does” or “why did you choose this approach?”
## Core Rules
1. **Explain the “what” first, then the “why.”**
- Start with a concise summary of the behavior or structure.
- Follow with the reasoning, constraints, or trade-offs that shaped it.
2. **Stay concrete and anchored to the code.**
- Reference file paths, function/class names, and key lines where relevant.
- Avoid vague or generic statements that could apply to any codebase.
3. **Match the audience.**
- For junior developers: explain domain concepts, naming choices, and control flow.
- For reviewers: emphasize trade-offs, risks, and alternatives considered.
- For non-technical stakeholders: translate the implementation into business impact.
4. **Be honest about limitations.**
- If a choice was made because of time, compatibility, or training-project constraints, say so.
- Do not invent or assume motivations not supported by the code or project context.
5. **Preserve project conventions.**
- In this repo, respect module boundaries (`catalog-core`, `catalog-api`, `catalog-import`, `catalog-app`, `frontend/src/...`).
- Do not introduce new frameworks, databases, or production-grade integrations just to make explanation easier.
## Explanation Template
For any non-trivial change, structure the explanation like this:
```markdown
## What is being implemented?
- Brief overview of the change (one to three sentences).
- Specific files/classes/functions affected.
- Inputs, outputs, and side effects.
## Why this approach?
- Problem being solved.
- Alternatives considered and why they were rejected.
- Constraints (stack, scope, demo nature, existing patterns).
- Trade-offs accepted (complexity, performance, readability, maintainability).
## How to verify
- Commands to run.
- Expected outcomes.
- Manual checks if relevant.
```
## Example Applications
### Backend (Quarkus)
When explaining a new endpoint, DTO, mapper, or service method:
- **What:** describe the resource path, HTTP method, request/response shapes, and which domain object it exposes.
- **Why:** explain MapStruct usage, immutability, why a DTO was introduced, and how it preserves traceability fields.
### Frontend (React + Redux Toolkit)
When explaining a new page, component, RTK Query hook, or slice:
- **What:** describe the route, UI states (loading/empty/error), data flow, and props.
- **Why:** explain the choice of RTK Query over a raw fetch, why Redux Toolkit state is shared, or why an Ant Design component was selected.
### Catalog Import (Apache POI)
When explaining importer logic:
- **What:** describe the sheet being read, the normalization steps, and the generated JSON structure.
- **Why:** explain why validation warnings are preferred over silent drops, why a fixed import date is used for training determinism, and how traceability fields are preserved.
## What to Avoid
- Pure code dumps without narrative.
- Jargon-heavy explanations that skip the actual behavior.
- Claims like “this is the best approach” without evidence or context.
- Misrepresenting demo/training constraints as production requirements.
- Adding explanation-only scaffolding (extra files, comments, or docs) that does not serve a clear reader.
## Verification
If the explanation accompanies a code change:
1. Re-read the explanation against the actual diff.
2. Confirm every claim about behavior is supported by the code.
3. Run the relevant tests or build commands listed in the target skill (e.g., `quarkus-catalog-backend`, `react-catalog-shop`).
4. Update the explanation if the code changes.
@@ -0,0 +1,136 @@
---
name: ndo-repro
description: Build an NDO microservice locally with Docker, push it to artifactory, deploy it to a dev env, then reproduce or validate the fix by driving the Business-Operation-Manager (BOM) API and reading live pod logs. Use when debugging or verifying a UNM-* ticket without waiting for CI, when the UI flow is hard to reproduce, when driving the replacement/Map-To/target-insert flow without a browser, or when the user says "repro via API", "drive BOM", "ship to <env>", "deploy my build to dev-2", "validate the fix on the cluster", "run ndo-repro in <env>". Covers env discovery across the saas-rnd-oss and ndo-shared clusters.
---
# NDO build → deploy → repro loop
Full loop on one env, no CI wait: build the service locally, push to artifactory, repoint the k8s deployment, then drive BOM's API and read pod logs to prove the ticket's acceptance criteria.
Two scripts, both env-aware via `-e <alias>`:
- `~/.claude/skills/ndo-repro/ndo-ship.sh` — doctor / test / build / push / deploy / status / rollback
- `~/.claude/skills/ndo-repro/ndo-api.sh` — env registry / auth / BOM API / logs
Run `--help` on either for the full command list.
## Envs
Aliases come from a discovered registry (`envs.tsv`, refreshed with `ndo-api.sh env discover` — it scans every kube context for a namespace running `consolidated-inventory-manager-v1` and reads the `public-gateway` ingress host).
```
ndo-api.sh env ls # alias → context / namespace / gateway
ndo-api.sh -e oss-01/dev-2 env show
```
Alias shape is `<cluster>/<env>` (`oss-01/dev-2`, `oss-03/dev-1`) plus `shared-244` for `ndo-shared-244/ndo`. A bare `dev-2` is accepted **only** if it is unique across clusters; otherwise the script lists the candidates and stops — never guess which cluster the user meant, ask.
Everything needs the corporate VPN. `ndo-dev-1` is decommissioned; do not use it.
## Step 0 — preflight
```
ndo-ship.sh doctor -e <env>
```
Checks docker/OrbStack, buildx, artifactory login, host arch, and kube access for the env. If it reports "NOT logged in": `ndo-ship.sh login` (interactive artifactory password prompt — the user runs it, prefix with `!` in the CLI).
## Step 1 — build (tests first)
```
ndo-ship.sh build <service> [--ticket 231239] [--skip-tests] [--no-cache]
```
- Runs unit tests first — Maven `mvn -B test` for Java services, the dockerfile's `test` stage or `go test ./...` for Go — and aborts the build if they fail. Do not pass `--skip-tests` when the user asked for "build and unit tests successful".
- Java services: runs `mvn -B -DskipTests package` after the tests so `target/*.jar` exists for the `COPY`.
- Builds `--platform linux/amd64`. **Never drop this** — the Mac is arm64, the nodes are amd64, and the mismatch only surfaces as a crashlooping pod after deploy.
- Uses `Dockerfile_local` if present, else `Dockerfile`, and `--target release` when the dockerfile has stages. See `reference/dockerfile-local.md` before writing one.
- Image ref: `artifactorycn.netcracker.com:17009/<artifactory-user>/<service>_unm_<ticket>:<utc-timestamp>`. Ticket is parsed from the git branch (`bugfix/UNM-231239``231239`). The timestamp tag matters: deployments run `imagePullPolicy: IfNotPresent`, so a reused tag silently keeps the old image.
The ref is cached, so `push`/`deploy` need no `--tag`.
## Step 2 — push + deploy
```
ndo-ship.sh push <service>
ndo-ship.sh deploy <service> -e <env> --yes
# or all of it:
ndo-ship.sh ship <service> -e <env> --yes
```
`deploy` records the currently deployed image as a rollback point, `kubectl set image`s the deployment, and waits for `rollout status`. On failure it dumps pod state.
**`deploy`/`ship`/`rollback`/`pullsecret` mutate a shared env.** They refuse to run without `--yes`, and `--yes` is only yours to pass after the user has approved *that* deploy to *that* env. Approval for one env or one ticket does not carry over.
Rollback: `ndo-ship.sh rollback <service> -e <env> --yes`.
If pods go `ImagePullBackOff`, the nodes have no credentials for the `:17009` personal repo:
```
ndo-ship.sh pullsecret <service> -e <env> --yes
```
which creates a `docker-registry` secret from the local docker keychain and patches the deployment's `imagePullSecrets`.
## Step 3 — confirm what is actually running
The single most common cause of "the fix didn't work" is the wrong image.
```
ndo-api.sh -e <env> image <service>
ndo-api.sh -e <env> pods <service>
```
Match the tag to the build you just pushed. Product images look like `…:release_2024.4_<date>`; yours look like `…/<user>/<service>_unm_<ticket>:<timestamp>`.
## Step 4 — drive the BOM API
Auth is automatic and per-env: a keycloak password-grant token (realm `default`, client `frontend`, dev sysadm creds) is minted and refreshed on expiry. Override with `NDO_USER` / `NDO_PASS` / `NDO_REALM` / `NDO_CLIENT`. Tokens live in `~/.cache/ndo-repro/token-<env>.txt`, mode 600 — never echo one into chat or a committed file.
Stateful operation lifecycle (BOM `/business-operation-manager/v1`):
- **initiate**: `POST /operation-request/initiate?key=<opKey>` → returns `operation-request-id` (rid).
- **prepare a sub-operation**: `POST /operation-request/{rid}/prepare?key=<subOpKey>` with `{data, sources, parent-path}` (BOM injects operation-data/inputs from the session).
- **perform a read/action**: `POST /operation-request/{rid}/perform` with `{"method":"GET","url":"/consolidated-inventory-manager/v3/<path>","body":{…}}` — the inner call is wrapped.
Replacement (CIM `/v3/replacement`) endpoints, all via `perform` GET:
- `/report` — impact summary; `resolved-issues` / `unresolved-issues` is the pass/fail metric.
- `/target` — target tree (chassis + slots; does **not** expose ports/interfaces).
- `/target/slots` — slots for a target component.
- `/mapping`, `/mapping/available-target-values` — Map-To candidates (`{impact-type, impacted-entity-mkey, ref-endpoint-mkey, [filter], [only-total]}`); `total:0` = "No available interfaces".
- target insert sub-op key: `nc_op_ci_<as-is|to-be>_hw-component.replacement.target.insert.module`.
Finding ids: `/report` gives source/target mkeys; `/target` gives chassis + slot ids; a DL spec read (`/device-library/v1/restconf/data/hw-component?depth=3&filter=[{op:eq,property:id,value:[<srcId>]}]`) gives `port-interface`/`port-type`.
```
ndo-api.sh -e <env> initiate nc_op_ci_as-is_hw-component.replacement
ndo-api.sh -e <env> report <rid>
ndo-api.sh -e <env> avail <rid> <impactMkey> <refMkey>
ndo-api.sh -e <env> get <rid> /v3/replacement/target
```
## Step 5 — read live logs (ground truth)
```
ndo-api.sh -e <env> logs consolidated-inventory-manager 15m '\[UNM-231239\]'
```
Strips `tenant_id`/`thread`/`traceId`/`spanId`/`request_id` noise. Grep the ticket tag for the dev's INFO traces plus `WARN`/`ERROR`; correlate one call end to end by `request_id=` (drop the sed filter when you need it).
Known noise to ignore: `Unknown token audience: netcracker` — a k8s m2m quirk on the dev envs, not your bug unless the user says otherwise.
## Validating acceptance criteria
When asked to "validate the issue is resolved and acceptance criteria fulfilled", the deliverable is evidence, not an opinion:
1. State the deployed image tag and prove it is your build.
2. For each acceptance criterion, name the API call that exercises it and show the response field that decides pass/fail (e.g. `unresolved-issues: 0`, `total > 0`).
3. Show the log lines that confirm the new code path ran.
4. Report any criterion you could **not** exercise, and why — do not infer a pass from an adjacent one.
## Safety
- Read-mostly on the API side. `prepare`/`perform` writes mutate only the draft stateful session — fine for repro. Do not `/complete` a replacement unless asked.
- Deploying replaces a running service other people may be using. Confirm the env with the user first, keep the rollback point, and roll back when done if they asked you to.
- Never push to `:17099`/`:17003` (product repos) — `:17009` personal only.
- Never open MRs, push branches, or change CI without explicit approval.
- If a stateful session is polluted by earlier inserts, initiate a fresh rid rather than fighting old state.
## Pattern that works
fix in source → `ndo-ship.sh build` (tests gate it) → `push` → confirm env with user → `deploy --yes` → verify image tag → initiate/drive the exact sub-op the UI would → read the report metric → if it still fails, read CIM logs for the real reason → new hypothesis → repeat.
## Media (when QA attaches gifs/videos)
- GIF frames: Python+PIL (`Image.open(g); im.seek(i)`); crop the devtools network panel and upscale to read request names/statuses.
- Video: `ffmpeg -i in.mp4 -vf fps=1/5 out%03d.jpg`, then narrow with `-ss <start> -to <end> -vf fps=1`.
@@ -0,0 +1,15 @@
oss-01/dev-1 pedro.aranha-saas-rnd-oss-01 dev-1-oss https://public-gateway-dev-1-oss.saas-rnd-oss-01.managed.netcracker.cloud
oss-01/dev-2 pedro.aranha-saas-rnd-oss-01 dev-2-oss https://public-gateway-dev-2-oss.saas-rnd-oss-01.managed.netcracker.cloud
oss-01/dev-3 pedro.aranha-saas-rnd-oss-01 dev-3-oss https://public-gateway-dev-3-oss.saas-rnd-oss-01.managed.netcracker.cloud
oss-01/dev-4 pedro.aranha-saas-rnd-oss-01 dev-4-oss https://public-gateway-dev-4-oss.saas-rnd-oss-01.managed.netcracker.cloud
oss-02/dev-0 pedro.aranha-saas-rnd-oss-02 dev-0-oss https://public-gateway-dev-0-oss.saas-rnd-oss-02.managed.netcracker.cloud
oss-02/dev-2 pedro.aranha-saas-rnd-oss-02 dev-2-oss https://public-gateway-dev-2-oss.saas-rnd-oss-02.managed.netcracker.cloud
oss-02/dev-3 pedro.aranha-saas-rnd-oss-02 dev-3-oss https://public-gateway-dev-3-oss.saas-rnd-oss-02.managed.netcracker.cloud
oss-02/dev-4 pedro.aranha-saas-rnd-oss-02 dev-4-oss https://public-gateway-dev-4-oss.saas-rnd-oss-02.managed.netcracker.cloud
oss-03/dev-0 pedro.aranha-saas-rnd-oss-03 dev-0-oss https://public-gateway-dev-0-oss.saas-rnd-oss-03.managed.netcracker.cloud
oss-03/dev-1 pedro.aranha-saas-rnd-oss-03 dev-1-oss https://public-gateway-dev-1-oss.saas-rnd-oss-03.managed.netcracker.cloud
oss-03/dev-2 pedro.aranha-saas-rnd-oss-03 dev-2-oss https://public-gateway-dev-2-oss.saas-rnd-oss-03.managed.netcracker.cloud
oss-03/dev-3 pedro.aranha-saas-rnd-oss-03 dev-3-oss https://public-gateway-dev-3-oss.saas-rnd-oss-03.managed.netcracker.cloud
shared-244 ndo-shared-244 ndo https://public-gateway-ndo.ndo-shared-244.managed.netcracker.cloud
shared-244/ndo-at ndo-shared-244 ndo-at https://public-gateway-ndo-at.ndo-shared-244.managed.netcracker.cloud
shared-244/ndo-dev ndo-shared-244 ndo-dev https://public-gateway-ndo-dev.ndo-shared-244.managed.netcracker.cloud
1 oss-01/dev-1 pedro.aranha-saas-rnd-oss-01 dev-1-oss https://public-gateway-dev-1-oss.saas-rnd-oss-01.managed.netcracker.cloud
2 oss-01/dev-2 pedro.aranha-saas-rnd-oss-01 dev-2-oss https://public-gateway-dev-2-oss.saas-rnd-oss-01.managed.netcracker.cloud
3 oss-01/dev-3 pedro.aranha-saas-rnd-oss-01 dev-3-oss https://public-gateway-dev-3-oss.saas-rnd-oss-01.managed.netcracker.cloud
4 oss-01/dev-4 pedro.aranha-saas-rnd-oss-01 dev-4-oss https://public-gateway-dev-4-oss.saas-rnd-oss-01.managed.netcracker.cloud
5 oss-02/dev-0 pedro.aranha-saas-rnd-oss-02 dev-0-oss https://public-gateway-dev-0-oss.saas-rnd-oss-02.managed.netcracker.cloud
6 oss-02/dev-2 pedro.aranha-saas-rnd-oss-02 dev-2-oss https://public-gateway-dev-2-oss.saas-rnd-oss-02.managed.netcracker.cloud
7 oss-02/dev-3 pedro.aranha-saas-rnd-oss-02 dev-3-oss https://public-gateway-dev-3-oss.saas-rnd-oss-02.managed.netcracker.cloud
8 oss-02/dev-4 pedro.aranha-saas-rnd-oss-02 dev-4-oss https://public-gateway-dev-4-oss.saas-rnd-oss-02.managed.netcracker.cloud
9 oss-03/dev-0 pedro.aranha-saas-rnd-oss-03 dev-0-oss https://public-gateway-dev-0-oss.saas-rnd-oss-03.managed.netcracker.cloud
10 oss-03/dev-1 pedro.aranha-saas-rnd-oss-03 dev-1-oss https://public-gateway-dev-1-oss.saas-rnd-oss-03.managed.netcracker.cloud
11 oss-03/dev-2 pedro.aranha-saas-rnd-oss-03 dev-2-oss https://public-gateway-dev-2-oss.saas-rnd-oss-03.managed.netcracker.cloud
12 oss-03/dev-3 pedro.aranha-saas-rnd-oss-03 dev-3-oss https://public-gateway-dev-3-oss.saas-rnd-oss-03.managed.netcracker.cloud
13 shared-244 ndo-shared-244 ndo https://public-gateway-ndo.ndo-shared-244.managed.netcracker.cloud
14 shared-244/ndo-at ndo-shared-244 ndo-at https://public-gateway-ndo-at.ndo-shared-244.managed.netcracker.cloud
15 shared-244/ndo-dev ndo-shared-244 ndo-dev https://public-gateway-ndo-dev.ndo-shared-244.managed.netcracker.cloud
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
# Shared env resolution for the ndo-repro skill. Source this; do not execute.
# Exports NDO_CTX (kube context), NDO_NS (namespace), NDO_GW (gateway base URL).
NDO_CACHE="${NDO_CACHE:-$HOME/.cache/ndo-repro}"
NDO_ENV_FILE="${NDO_ENV_FILE:-$NDO_CACHE/envs.tsv}"
NDO_ENV_SEED="${NDO_ENV_SEED:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/envs.tsv}"
NDO_MARKER="${NDO_MARKER:-consolidated-inventory-manager-v1}"
NDO_NS_RE="${NDO_NS_RE:-^(ndo|ndo-dev|ndo-at|dev-[0-9]+-oss)$}"
_ndo_die() { echo "$*" >&2; exit 2; }
env_file() {
[ -s "$NDO_ENV_FILE" ] && { echo "$NDO_ENV_FILE"; return; }
mkdir -p "$NDO_CACHE"
[ -s "$NDO_ENV_SEED" ] && cp "$NDO_ENV_SEED" "$NDO_ENV_FILE"
echo "$NDO_ENV_FILE"
}
env_list() {
printf '%-16s %-34s %-14s %s\n' ALIAS CONTEXT NAMESPACE GATEWAY
awk -F'\t' '!/^#/ && NF>=4 {printf "%-16s %-34s %-14s %s\n",$1,$2,$3,$4}' "$(env_file)"
}
# env_resolve <alias> -> sets NDO_CTX / NDO_NS / NDO_GW
env_resolve() {
local want="${1:-}" f hits n
[ -n "$want" ] || _ndo_die "no env given. Use -e <alias> or NDO_ENV=<alias>. Known:
$(env_list)"
f="$(env_file)"
hits=$(awk -F'\t' -v w="$want" '!/^#/ && NF>=4 && ($1==w || $1 ~ "/" w "$")' "$f")
n=$(printf '%s' "$hits" | grep -c . || true)
[ "$n" -eq 0 ] && _ndo_die "unknown env '$want'. Known:
$(env_list)
Run: ndo-api.sh env discover"
[ "$n" -gt 1 ] && _ndo_die "ambiguous env '$want' — matches:
$(printf '%s\n' "$hits" | cut -f1)
Use the full alias (e.g. oss-01/$want)."
NDO_CTX=$(printf '%s' "$hits" | cut -f2)
NDO_NS=$(printf '%s' "$hits" | cut -f3)
NDO_GW=$(printf '%s' "$hits" | cut -f4)
export NDO_CTX NDO_NS NDO_GW
}
# Short cluster alias: pedro.aranha-saas-rnd-oss-01 -> oss-01 ; ndo-shared-244 -> shared-244
_cluster_alias() { sed -E 's/^.*saas-rnd-//; s/^ndo-//' <<<"$1"; }
# Short env alias: dev-1-oss -> dev-1 ; ndo -> (cluster alias only)
_ns_alias() { sed -E 's/-oss$//' <<<"$1"; }
env_discover() {
local out ctx nss ns host alias calias nalias
mkdir -p "$NDO_CACHE"
out="$NDO_CACHE/envs.tsv.new"
: > "$out"
for ctx in $(kubectl config get-contexts -o name 2>/dev/null); do
case "$ctx" in orbstack|docker-desktop|minikube|kind-*) continue ;; esac
nss=$(timeout 25 kubectl --context="$ctx" get ns -o name 2>/dev/null | sed 's|namespace/||' | grep -E "$NDO_NS_RE") || continue
calias=$(_cluster_alias "$ctx")
for ns in $nss; do
timeout 20 kubectl --context="$ctx" -n "$ns" get deploy "$NDO_MARKER" -o name >/dev/null 2>&1 || continue
host=$(timeout 20 kubectl --context="$ctx" -n "$ns" get ingress public-gateway \
-o jsonpath='{.spec.rules[0].host}' 2>/dev/null)
[ -n "$host" ] || host="public-gateway-${ns}.$(sed -E 's/^.*(saas-rnd-[a-z0-9-]+|ndo-[a-z0-9-]+)$/\1/' <<<"$ctx").managed.netcracker.cloud"
nalias=$(_ns_alias "$ns")
if [ "$nalias" = "ndo" ]; then alias="$calias"; else alias="$calias/$nalias"; fi
printf '%s\t%s\t%s\thttps://%s\n' "$alias" "$ctx" "$ns" "$host" >> "$out"
echo "found $alias -> $ctx/$ns" >&2
done
done
[ -s "$out" ] || _ndo_die "discovery found no envs (VPN down? kube creds expired?) — kept $NDO_ENV_FILE"
sort -o "$out" "$out"
mv "$out" "$NDO_ENV_FILE"
env_list
}
@@ -0,0 +1,161 @@
#!/usr/bin/env bash
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/env.sh
source "$HERE/lib/env.sh"
ENV_ALIAS="${NDO_ENV:-}"
# -e/--env may appear anywhere; strip it before dispatch.
ARGS=()
while [ $# -gt 0 ]; do
case "$1" in
-e|--env) ENV_ALIAS="$2"; shift 2 ;;
*) ARGS+=("$1"); shift ;;
esac
done
set -- "${ARGS[@]:-}"
NDO_REALM="${NDO_REALM:-default}"
NDO_CLIENT="${NDO_CLIENT:-frontend}"
NDO_USER="${NDO_USER:?Set NDO_USER through an approved configuration source before using authenticated API commands}"
NDO_PASS="${NDO_PASS:?Set NDO_PASS through an approved secret source before using authenticated API commands}"
usage() {
cat <<'USAGE'
ndo-api.sh — drive the NDO BOM API for live repro, on any registered env.
Every command needs a target env: -e <alias> (or NDO_ENV=<alias>).
Auth is automatic: a keycloak password-grant token is minted per env and
refreshed on expiry (~15 min). Token cache: ~/.cache/ndo-repro/token-<env>.
Env:
env ls list registered envs
env discover rescan kube contexts, rebuild the registry
env show resolved context / namespace / gateway for -e
API:
login mint a fresh token now
token <jwt> save an externally-supplied bearer token
whoami check auth (200 = ok)
opdef <key> GET operation-definition for an op key
initiate <key> [bodyfile] POST initiate, prints operation-request-id
perform <rid> <innerJsonOrFile> POST /{rid}/perform with a wrapped {method,url,body}
prepare <rid> <key> <bodyfile> POST /{rid}/prepare?key=<key> with body file
get <rid> <cimPath> [innerBodyJson] perform a GET against /consolidated-inventory-manager<cimPath>
report <rid> replacement report (resolved/unresolved)
target <rid> replacement target tree
avail <rid> <impactMkey> <refMkey> [type] available-target-values (type default l2_link)
Cluster:
logs <service> [since] [grep] tail+denoise logs (default since=10m)
image <service> deployed image of <service>-v1
pods <service> pod phase/restarts for <service>-v1
Examples:
ndo-api.sh env ls
ndo-api.sh -e shared-244 whoami
ndo-api.sh -e oss-01/dev-2 report 21dec51b-f9cb-41fe-af94-512c0921036b
ndo-api.sh -e oss-01/dev-2 logs consolidated-inventory-manager 15m '\[UNM-231239\]'
USAGE
}
case "${1:-}" in
""|-h|--help|help) usage; exit 0 ;;
env)
case "${2:-ls}" in
ls|list) env_list; exit 0 ;;
discover) env_discover; exit 0 ;;
show) env_resolve "$ENV_ALIAS"; printf 'alias : %s\ncontext : %s\nns : %s\ngateway : %s\n' \
"$ENV_ALIAS" "$NDO_CTX" "$NDO_NS" "$NDO_GW"; exit 0 ;;
*) echo "env: ls | discover | show" >&2; exit 2 ;;
esac ;;
esac
env_resolve "$ENV_ALIAS"
GW="${NDO_GW_OVERRIDE:-$NDO_GW}"
BOM="$GW/business-operation-manager/v1"
mkdir -p "$NDO_CACHE"
TOKFILE="${NDO_TOKEN_FILE:-$NDO_CACHE/token-$(tr '/' '_' <<<"$ENV_ALIAS").txt}"
mint() {
local out
out=$(curl -sk -X POST "$GW/auth/realms/$NDO_REALM/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=password" --data-urlencode "client_id=$NDO_CLIENT" \
--data-urlencode "username=$NDO_USER" --data-urlencode "password=$NDO_PASS")
printf '%s' "$out" | python3 -c "import sys,json;d=json.load(sys.stdin);open('$TOKFILE','w').write(d['access_token']) if 'access_token' in d else sys.exit('mint failed: '+json.dumps(d)[:200])" || return 1
chmod 600 "$TOKFILE"
}
token_valid() {
[ -s "$TOKFILE" ] || return 1
python3 - "$TOKFILE" <<'PY' 2>/dev/null
import sys,base64,json,time
t=open(sys.argv[1]).read().strip()
p=t.split('.')[1]; p+='='*(-len(p)%4)
exp=json.loads(base64.urlsafe_b64decode(p)).get('exp',0)
sys.exit(0 if exp-time.time()>30 else 1)
PY
}
ensure_token() { token_valid || mint; }
tok() { cat "$TOKFILE"; }
auth() { ensure_token >&2 || { echo "auth failed on $ENV_ALIAS" >&2; exit 1; }; echo "Authorization: Bearer $(tok)"; }
K() { kubectl --context="$NDO_CTX" -n "$NDO_NS" "$@"; }
# Services use either app=<svc>-v1 or name=<svc>-v1 depending on the chart.
selector_for() {
local svc="$1" l
for l in "app=$svc-v1" "name=$svc-v1" "app=$svc" "name=$svc"; do
[ -n "$(K get pod -l "$l" -o name 2>/dev/null)" ] && { echo "$l"; return 0; }
done
echo "no pods for $svc (tried app=/name= selectors) in $NDO_NS" >&2
return 1
}
case "${1:-}" in
token) printf '%s' "$2" > "$TOKFILE"; chmod 600 "$TOKFILE"; echo "saved to $TOKFILE"; ;;
login) mint && echo "minted ($NDO_USER, realm=$NDO_REALM, env=$ENV_ALIAS) → $TOKFILE" ;;
whoami) curl -sk -o /dev/null -w "HTTP %{http_code}\n" -H "$(auth)" "$BOM/operation-definition?key=nc_op_ci_as-is_hw-component.replacement" ;;
opdef) curl -sk -H "$(auth)" "$BOM/operation-definition?key=$2" ;;
initiate)
body="${3:-{} }"; [ -f "${3:-}" ] && body="@$3"
curl -sk -X POST -H "$(auth)" -H 'Content-Type: application/json' "$BOM/operation-request/initiate?key=$2" -d "$body" ;;
perform)
inner="$3"; [ -f "$3" ] && inner="@$3"
curl -sk -X POST -H "$(auth)" -H 'Content-Type: application/json' "$BOM/operation-request/$2/perform" -d "$inner" ;;
prepare)
curl -sk -X POST -H "$(auth)" -H 'Content-Type: application/json' "$BOM/operation-request/$2/prepare?key=$3" -d "@$4" ;;
get)
rid="$2"; path="$3"; innerbody="${4:-}"
if [ -n "$innerbody" ]; then req="{\"method\":\"GET\",\"url\":\"/consolidated-inventory-manager$path\",\"body\":$innerbody}";
else req="{\"method\":\"GET\",\"url\":\"/consolidated-inventory-manager$path\"}"; fi
curl -sk -X POST -H "$(auth)" -H 'Content-Type: application/json' "$BOM/operation-request/$rid/perform" -d "$req" ;;
report)
curl -sk -X POST -H "$(auth)" -H 'Content-Type: application/json' "$BOM/operation-request/$2/perform" \
-d '{"method":"GET","url":"/consolidated-inventory-manager/v3/replacement/report"}' \
| python3 -c "import sys,json;i=json.load(sys.stdin).get('action-report',{}).get('results',{}).get('impact',[]);print(json.dumps(i,indent=1))" ;;
target)
curl -sk -X POST -H "$(auth)" -H 'Content-Type: application/json' "$BOM/operation-request/$2/perform" \
-d '{"method":"GET","url":"/consolidated-inventory-manager/v3/replacement/target"}' ;;
avail)
typ="${5:-l2_link}"
curl -sk -X POST -H "$(auth)" -H 'Content-Type: application/json' "$BOM/operation-request/$2/perform" \
-d "{\"method\":\"GET\",\"url\":\"/consolidated-inventory-manager/v3/replacement/mapping/available-target-values\",\"body\":{\"impact-type\":\"$typ\",\"impacted-entity-mkey\":\"$3\",\"ref-endpoint-mkey\":\"$4\"}}" \
| python3 -c "import sys,json;r=json.load(sys.stdin).get('action-report',{}).get('results',{});print('total',r.get('total'),'values',len(r.get('available-values',[])))" ;;
logs)
svc="$2"; since="${3:-10m}"; pat="${4:-}"
SEL=$(selector_for "$svc") || exit 1
P=$(K get pod -l "$SEL" -o jsonpath='{.items[0].metadata.name}')
K logs "$P" --since="$since" 2>/dev/null \
| sed -E 's/\[(tenant_id|thread|originating_bi_id|traceId|spanId|request_id)=[^]]*\] ?//g' \
| { [ -n "$pat" ] && grep -aE "$pat" || cat; } ;;
image)
K get deploy "$2-v1" -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}' ;;
pods)
SEL=$(selector_for "$2") || exit 1
K get pod -l "$SEL" -o custom-columns='POD:.metadata.name,PHASE:.status.phase,READY:.status.containerStatuses[0].ready,RESTARTS:.status.containerStatuses[0].restartCount,IMAGE:.status.containerStatuses[0].image' ;;
*) echo "unknown cmd: $1"; usage; exit 1 ;;
esac
@@ -0,0 +1,277 @@
#!/usr/bin/env bash
# Build a NDO service locally with Docker, push to artifactory, point a k8s deployment at it.
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/env.sh
source "$HERE/lib/env.sh"
REG="${NDO_REGISTRY:-artifactorycn.netcracker.com:17009}"
ART_USER="${NDO_ARTIFACTORY_USER:-$USER}"
PLATFORM="${NDO_PLATFORM:-linux/amd64}"
PROJECTS="${NDO_PROJECTS:-$HOME/projects}"
ENV_ALIAS="${NDO_ENV:-}"
SVC=""; DIR=""; TAG=""; TICKET=""; DFILE=""; TARGET="release"
YES=0; NOCACHE=0; SKIP_TESTS=0; TIMEOUT="10m"
die() { echo "ERROR: $*" >&2; exit 1; }
say() { echo "==> $*" >&2; }
usage() {
cat <<'USAGE'
ndo-ship.sh — local build → artifactory → k8s deploy for NDO services.
Commands:
doctor check docker/buildx/registry-login/kubectl
login docker login to artifactory (interactive)
tag <service> print the image ref that would be built
test <service> run unit tests only (maven, or docker --target test)
build <service> build the image (runs unit tests first unless --skip-tests)
push <service> push the last built (or --tag'd) image
deploy <service> -e ENV point <service>-v1 at the image + wait for rollout [needs --yes]
ship <service> -e ENV test → build → push → deploy → rollout wait [needs --yes]
status <service> -e ENV deployed image, replicas, pod state
rollback <service> -e ENV restore the image recorded before the last deploy [needs --yes]
pullsecret <service> -e ENV attach local docker creds as an imagePullSecret (ImagePullBackOff fix) [needs --yes]
Options:
-e, --env ALIAS target env (see: ndo-api.sh env ls). Ambiguous short names are rejected.
-t, --tag TAG image tag (default: UTC timestamp, always unique)
--ticket N UNM number for the repo name (default: parsed from git branch)
-d, --dir PATH service repo (default: $NDO_PROJECTS/<service>)
-f, --file FILE dockerfile (default: Dockerfile_local, falls back to Dockerfile)
--target STAGE build target (default: release; ignored if the dockerfile has no stages)
--platform P default linux/amd64 — do NOT drop this on an arm64 Mac
--skip-tests skip unit tests in build/ship
--no-cache docker build --no-cache
--timeout D rollout wait (default 10m)
-y, --yes confirm a cluster-mutating command (deploy/ship/rollback/pullsecret)
Image ref: $REG/<artifactory-user>/<service>_unm_<ticket>:<tag>
Env overrides: NDO_REGISTRY NDO_ARTIFACTORY_USER NDO_PLATFORM NDO_PROJECTS NDO_ENV
USAGE
}
parse_opts() {
while [ $# -gt 0 ]; do
case "$1" in
-e|--env) ENV_ALIAS="$2"; shift 2 ;;
-t|--tag) TAG="$2"; shift 2 ;;
--ticket) TICKET="$2"; shift 2 ;;
-d|--dir) DIR="$2"; shift 2 ;;
-f|--file) DFILE="$2"; shift 2 ;;
--target) TARGET="$2"; shift 2 ;;
--platform) PLATFORM="$2"; shift 2 ;;
--timeout) TIMEOUT="$2"; shift 2 ;;
--skip-tests) SKIP_TESTS=1; shift ;;
--no-cache) NOCACHE=1; shift ;;
-y|--yes) YES=1; shift ;;
-*) die "unknown option $1" ;;
*) [ -z "$SVC" ] && SVC="$1" || die "unexpected arg $1"; shift ;;
esac
done
}
need_svc() { [ -n "$SVC" ] || die "no service given"; }
svc_dir() {
need_svc
[ -n "$DIR" ] || DIR="$PROJECTS/$SVC"
[ -d "$DIR" ] || die "service repo not found: $DIR (use --dir)"
echo "$DIR"
}
dockerfile() {
local d; d="$(svc_dir)"
if [ -n "$DFILE" ]; then [ -f "$d/$DFILE" ] || [ -f "$DFILE" ] || die "dockerfile not found: $DFILE"; echo "$DFILE"; return; fi
if [ -f "$d/Dockerfile_local" ]; then echo "Dockerfile_local"; return; fi
echo "Dockerfile"
echo "no Dockerfile_local in $d — using Dockerfile. If the build pulls shared/external artifacts, create Dockerfile_local (see reference/dockerfile-local.md)." >&2
}
ticket() {
[ -n "$TICKET" ] && { echo "$TICKET"; return; }
local d b; d="$(svc_dir)"
b=$(git -C "$d" branch --show-current 2>/dev/null || true)
if [[ "$b" =~ [Uu][Nn][Mm][-_]?([0-9]+) ]]; then echo "${BASH_REMATCH[1]}"; else echo "local"; fi
}
image_ref() {
need_svc
local t; t="${TAG:-$(date -u +%Y%m%d-%H%M%S)}"
echo "$REG/$ART_USER/${SVC}_unm_$(ticket):$t"
}
last_image_file() { mkdir -p "$NDO_CACHE/last-image"; echo "$NDO_CACHE/last-image/$SVC"; }
resolve_image() {
if [ -n "$TAG" ]; then image_ref; return; fi
local f; f="$(last_image_file)"
[ -s "$f" ] || die "no image built yet for $SVC — run 'build' first or pass --tag"
cat "$f"
}
confirm() {
[ "$YES" -eq 1 ] || die "'$1' mutates shared env '$ENV_ALIAS' (context $NDO_CTX, ns $NDO_NS). Re-run with --yes once the user has approved."
}
container_name() {
local names first
names=$(kubectl --context="$NDO_CTX" -n "$NDO_NS" get deploy "$SVC-v1" \
-o jsonpath='{range .spec.template.spec.containers[*]}{.name}{"\n"}{end}')
if grep -qx "$SVC" <<<"$names"; then echo "$SVC"; else first=$(head -1 <<<"$names"); [ -n "$first" ] || die "no containers in $SVC-v1"; echo "$first"; fi
}
current_image() {
kubectl --context="$NDO_CTX" -n "$NDO_NS" get deploy "$SVC-v1" \
-o jsonpath='{.spec.template.spec.containers[0].image}'
}
rollback_file() { mkdir -p "$NDO_CACHE/rollback"; echo "$NDO_CACHE/rollback/$(tr '/' '_' <<<"$ENV_ALIAS")__$SVC"; }
is_maven() { [ -f "$(svc_dir)/pom.xml" ]; }
is_go() { [ -f "$(svc_dir)/go.mod" ]; }
has_stages() { grep -qiE '^[[:space:]]*FROM .* AS ' "$(svc_dir)/$(dockerfile)"; }
copies_target() { grep -qE 'COPY .*target/' "$(svc_dir)/$(dockerfile)"; }
mvn_env() {
export JAVA_HOME="${JAVA_HOME:-/Library/Java/JavaVirtualMachines/jdk-25.0.2.jdk/Contents/Home}"
export PATH="$JAVA_HOME/bin:$PATH"
}
run_tests() {
local d; d="$(svc_dir)"
if is_maven; then
say "maven unit tests ($SVC)"
( mvn_env; cd "$d" && mvn -B test )
elif is_go && grep -qiE '^[[:space:]]*FROM .* AS test' "$d/$(dockerfile)"; then
say "docker test stage ($SVC)"
docker build --platform "$PLATFORM" -f "$d/$(dockerfile)" --target test -t "$SVC-test:local" "$d"
elif is_go; then
say "go test ($SVC)"
( cd "$d" && go test ./... )
else
say "no unit-test runner detected for $SVC — skipping"
fi
}
do_build() {
local d df img args=()
d="$(svc_dir)"; df="$(dockerfile)"; img="$(image_ref)"
[ "$SKIP_TESTS" -eq 1 ] || run_tests
# Java services copy target/*.jar into the image — package first.
if is_maven && copies_target; then
say "mvn package -DskipTests (jar for the image layer)"
( mvn_env; cd "$d" && mvn -B -DskipTests package )
fi
args=(build --platform "$PLATFORM" -f "$d/$df" -t "$img")
has_stages && grep -qiE "^[[:space:]]*FROM .* AS $TARGET\$" "$d/$df" && args+=(--target "$TARGET")
[ "$NOCACHE" -eq 1 ] && args+=(--no-cache)
args+=("$d")
say "docker ${args[*]}"
docker "${args[@]}"
echo "$img" > "$(last_image_file)"
echo "$img"
}
do_push() {
local img; img="$(resolve_image)"
say "docker push $img"
docker push "$img"
echo "$img"
}
do_deploy() {
local img c prev
env_resolve "$ENV_ALIAS"
confirm deploy
img="$(resolve_image)"
c="$(container_name)"
prev="$(current_image)"
echo "$prev" > "$(rollback_file)"
say "rollback point saved: $prev"
say "set image $SVC-v1/$c=$img (ctx=$NDO_CTX ns=$NDO_NS)"
kubectl --context="$NDO_CTX" -n "$NDO_NS" set image "deploy/$SVC-v1" "$c=$img"
kubectl --context="$NDO_CTX" -n "$NDO_NS" rollout status "deploy/$SVC-v1" --timeout="$TIMEOUT" || {
echo "--- rollout failed; pod events ---" >&2
kubectl --context="$NDO_CTX" -n "$NDO_NS" get pod -l "app=$SVC-v1" \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.phase}{"\t"}{range .status.containerStatuses[*]}{.state}{end}{"\n"}{end}' >&2
echo "ImagePullBackOff => node has no creds for $REG. Fix: ndo-ship.sh pullsecret $SVC -e $ENV_ALIAS --yes" >&2
return 1
}
do_status
}
do_status() {
env_resolve "$ENV_ALIAS"
need_svc
echo "env : $ENV_ALIAS (ctx=$NDO_CTX ns=$NDO_NS)"
echo "image : $(current_image)"
kubectl --context="$NDO_CTX" -n "$NDO_NS" get deploy "$SVC-v1" \
-o custom-columns='READY:.status.readyReplicas,DESIRED:.spec.replicas,UPDATED:.status.updatedReplicas'
kubectl --context="$NDO_CTX" -n "$NDO_NS" get pod -l "app=$SVC-v1" \
-o custom-columns='POD:.metadata.name,PHASE:.status.phase,RESTARTS:.status.containerStatuses[0].restartCount,AGE:.metadata.creationTimestamp'
}
do_rollback() {
local f prev c
env_resolve "$ENV_ALIAS"
confirm rollback
f="$(rollback_file)"
[ -s "$f" ] || die "no rollback point recorded for $SVC on $ENV_ALIAS"
prev="$(cat "$f")"; c="$(container_name)"
say "restoring $prev"
kubectl --context="$NDO_CTX" -n "$NDO_NS" set image "deploy/$SVC-v1" "$c=$prev"
kubectl --context="$NDO_CTX" -n "$NDO_NS" rollout status "deploy/$SVC-v1" --timeout="$TIMEOUT"
}
do_pullsecret() {
env_resolve "$ENV_ALIAS"
confirm pullsecret
local sec=ndo-repro-artifactory pw
pw=$(printf '%s' "$REG" | docker-credential-osxkeychain get 2>/dev/null \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["Secret"])') || die "no local docker creds for $REG — run: ndo-ship.sh login"
kubectl --context="$NDO_CTX" -n "$NDO_NS" create secret docker-registry "$sec" \
--docker-server="$REG" --docker-username="$ART_USER" --docker-password="$pw" \
--dry-run=client -o yaml | kubectl --context="$NDO_CTX" -n "$NDO_NS" apply -f -
unset pw
kubectl --context="$NDO_CTX" -n "$NDO_NS" patch deploy "$SVC-v1" \
-p "{\"spec\":{\"template\":{\"spec\":{\"imagePullSecrets\":[{\"name\":\"$sec\"}]}}}}"
kubectl --context="$NDO_CTX" -n "$NDO_NS" rollout status "deploy/$SVC-v1" --timeout="$TIMEOUT"
}
do_doctor() {
printf 'docker : %s\n' "$(docker version --format '{{.Server.Version}}' 2>&1 | head -1)"
printf 'context : %s\n' "$(docker context show 2>/dev/null)"
printf 'buildx : %s\n' "$(docker buildx version 2>&1 | head -1)"
printf 'host arch : %s (build platform %s)\n' "$(uname -m)" "$PLATFORM"
if printf '%s' "$REG" | docker-credential-osxkeychain get >/dev/null 2>&1; then
printf 'registry : logged in to %s as %s\n' "$REG" "$ART_USER"
else
printf 'registry : NOT logged in to %s — run: ndo-ship.sh login\n' "$REG"
fi
printf 'envs : %s\n' "$(awk -F'\t' '!/^#/&&NF>=4' "$(env_file)" | wc -l | tr -d ' ') registered"
[ -n "$ENV_ALIAS" ] && { env_resolve "$ENV_ALIAS"; printf 'env %-10s: ctx=%s ns=%s\n gw=%s\n' "$ENV_ALIAS" "$NDO_CTX" "$NDO_NS" "$NDO_GW"; \
kubectl --context="$NDO_CTX" -n "$NDO_NS" get deploy -o name >/dev/null 2>&1 \
&& echo 'kube access : ok' || echo 'kube access : FAILED (VPN down or creds expired)'; }
return 0
}
CMD="${1:-}"; shift || true
case "$CMD" in
doctor) parse_opts "$@"; do_doctor ;;
login) docker login "$REG" ;;
tag) parse_opts "$@"; image_ref ;;
test) parse_opts "$@"; run_tests ;;
build) parse_opts "$@"; do_build ;;
push) parse_opts "$@"; do_push ;;
deploy) parse_opts "$@"; do_deploy ;;
status) parse_opts "$@"; do_status ;;
rollback) parse_opts "$@"; do_rollback ;;
pullsecret) parse_opts "$@"; do_pullsecret ;;
ship) parse_opts "$@"; env_resolve "$ENV_ALIAS"; confirm ship
do_build >/dev/null; TAG=""; do_push >/dev/null; do_deploy ;;
""|-h|--help|help) usage ;;
*) die "unknown command: $CMD (see --help)" ;;
esac
@@ -0,0 +1,29 @@
# Reference copy: business-operation-manager Dockerfile_local (verified build 2026-08-12).
# Derived from the stock Dockerfile by dropping the "test" stage (needs ARANGO_DB_HOSTNAME)
# and the shared_resources COPY (CI-injected, absent locally).
# Copy to ~/projects/business-operation-manager/Dockerfile_local to use.
FROM artifactorycn.netcracker.com:17014/product/go-builder:1.26.4 AS base
ENV APP_ROOT=/tmp/project
COPY . ${APP_ROOT}
RUN chmod -R u+x ${APP_ROOT}/scripts && \
chmod -R u+x ${APP_ROOT}/*.sh && \
chgrp -R 0 ${APP_ROOT} && \
chmod -R g=u ${APP_ROOT} /etc/passwd
FROM base AS build
RUN cd ${APP_ROOT} && ${APP_ROOT}/application_build.sh
FROM artifactorycn.netcracker.com:17152/netcracker/qubership-core-base:2.3.7 AS release
COPY --chown=10001:10001 --from=build /tmp/project/scripts/* /bin/
COPY --chown=10001:10001 --from=build /tmp/project/business-operation-manager /bin/app
COPY --chown=10001:10001 --from=build /tmp/project/resources/policies.conf /opt/policies/
COPY --chown=10001:10001 --from=build /tmp/project/resources/business-operation-manager-public-api.json /opt/resources/business-operation-manager-public-api.json
EXPOSE 8080
USER 10001:10001
CMD [ "/bin/app" ]
@@ -0,0 +1,50 @@
# Dockerfile_local
`Dockerfile_local` is the CI `Dockerfile` with the parts that only work on a Jenkins agent removed, so it builds on a laptop. Upstream example (Go service):
<https://git.netcracker.com/PROD.INMRND.UNM/object-group-manager/-/blob/master/Dockerfile_local>
Create one only when the plain `Dockerfile` fails locally. `ndo-ship.sh` picks `Dockerfile_local` automatically when present, otherwise falls back to `Dockerfile`.
## What to strip from the CI Dockerfile
- `COPY`/`ADD` of shared resources, config bundles, or licence files injected by the pipeline.
- `ARG`s the pipeline fills (DB hosts, wiremock hosts, credentials) — hardcode a dev value or drop the stage.
- Integration/`test` stages that need Mongo/Postgres/Arango/Kafka. Keep pure unit tests only, or run tests outside Docker.
- `test-report` / coverage export stages — dead weight for a repro image.
## What must stay
- A stage named `release``ndo-ship.sh` builds `--target release` when the dockerfile has stages.
- The runtime base image and every `COPY` that puts the binary/jar plus its runtime resources in place.
## Java / Maven services (CIM, device-library, …)
Their `Dockerfile` is single-stage and copies a prebuilt jar:
```dockerfile
COPY --chown=10001:10001 target/consolidated-inventory-manager*.jar /app/app.jar
```
`ndo-ship.sh` detects `pom.xml` + a `COPY … target/` line and runs `mvn -B -DskipTests package` before `docker build`, so the jar exists. No `Dockerfile_local` is needed unless the base image or an `apk` mirror is unreachable from the laptop.
If the `apk add` step fails (internal `yumsrv03cn` mirror unreachable off-VPN), that layer only installs fonts — a `Dockerfile_local` that drops it is fine for a repro image:
```dockerfile
FROM artifactorycn.netcracker.com:17003/alpine/openjdk17:17.0.18.8.03 AS release
USER root
COPY --chown=10001:10001 target/consolidated-inventory-manager*.jar /app/app.jar
USER 10001:10001
CMD ["java", "-jar", "/app/app.jar"]
```
A working example that built and deployed cleanly is kept alongside this file: `bom-Dockerfile_local.example` (business-operation-manager, verified 2026-08-12).
## Go services (BOM, monitoring-*, …)
Already multi-stage with `base` / `test` / `build` / `release`. The usual local-only edits: drop the `test` stage's external `ARG` hosts, and drop `COPY … /shared_resources` if the pipeline generates it.
The Go build stages already pin `GOARCH=amd64`, so they cross-compile fine, but the **runtime** stage still needs `--platform linux/amd64` (see below).
## Architecture — the trap
The Mac is arm64; the clusters are amd64. Without `--platform linux/amd64` the image builds and pushes fine, then the pod dies with `exec format error` or `no match for platform in manifest`. `ndo-ship.sh` passes `--platform linux/amd64` by default; do not remove it.
An amd64 build on an arm64 host runs under emulation, so the maven/go steps inside Docker are slow. That is why `ndo-ship.sh` runs Maven natively on the host and only the image assembly under Docker.
## Registry
`artifactorycn.netcracker.com:17009` is the personal/dev repo — images land under `<artifactory-user>/…`. Product images live in `:17099` and `:17003`; never push there.
@@ -0,0 +1,53 @@
---
name: duplicate-code-check
description: >-
Find and report code duplication introduced by a merge request.
Use when asked to check for duplicates, repeated logic, or copy-paste code
in a branch or MR diff.
---
# Duplicate Code Check
Scans the diff of a branch or merge request for duplicated logic and produces a structured report with locations, severity and suggested actions. Does not remove any code without explicit user approval.
## Input
User should provide:
- branch name where duplication check should be done
- target branch name to compare
## Steps
1. Fetch the diff for the branch or MR
2. Scan the diff for duplicate blocks. Use criteria:
- identical or near-identical method bodies (more than 10 lines);
- copy-pasted conditional blocks or switch/case arms;
- repeated string literals or constants that could be extracted;
- utility functions that already exist elsewhere in the codebase.
3. Ask the user before suggesting any removal
4. Write the report.
## Output format
Create the file `duplication-check-<branch name>.md` with the table with next columns:
- all duplications
- risk level of removing each code duplication
- user decision is necessary.
## Rules
- Always check that safe delete is being suggested and there are no usages of removed code.
- Always ask before removing any code duplication.
## Passing criteria
The skill is complete only if:
- all files in the diff were scanned;
- no code was modified without explicit user approval;
- the report is written to `duplication-check-<branch name>.md` file.
@@ -0,0 +1 @@
![image](image.png)
@@ -0,0 +1,34 @@
---
name: am-i-free
description: Check whether the user has served their 4 hours at the Long Day Factory and can go home. Reads ~/long-day-factory.json, subtracts the lunch break from time in the office, and reports remaining time (or freedom) with a message of comfort. Use when the user asks "am I free", "can I go home", "how long have I been here".
---
# am-i-free
Does the math: **time served = (now startTime) lunch break**. The user is
free once time served reaches **4 hours**.
## Steps
1. Run:
```bash
python3 "$CLAUDE_SKILL_DIR/am_i_free.py"
```
Fallback path: `~/.claude/skills/am-i-free/am_i_free.py`.
2. Handle the exit code:
- **Exit 3** — `startTime` missing. Tell the user to run `long-day-start`.
- **Exit 2** — `NEEDS_LUNCH_DECISION`. The user probably forgot to log lunch.
Ask which they want:
- assume the standard **11:3012:30** lunch and save it →
`python3 "$CLAUDE_SKILL_DIR/am_i_free.py" --default-lunch`
- assume a flat **1h** lunch without saving →
`python3 "$CLAUDE_SKILL_DIR/am_i_free.py" --flat-hour`
- **Exit 0** — read the output.
3. Deliver the verdict with humor and a genuine message of comfort:
- **FREE**: congratulate them, tell them the overtime damage, send them home.
- **NOT FREE**: give the remaining time and the "parole at HH:MM" clock time,
and offer some dark encouragement to keep them going.
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Am I free to leave the Long Day Factory yet?
Time served = (now - startTime) - lunchBreak
You are free once time served reaches 4 hours.
Exit codes:
0 calculation done (see FREE / NOT FREE in output)
2 lunch times missing and no decision flag passed -> ask the user
3 startTime missing -> user must run long-day-start
"""
import json
import sys
from datetime import datetime, timedelta, time
from pathlib import Path
SENTENCE = timedelta(hours=4)
DEFAULT_LUNCH_OUT = time(11, 30)
DEFAULT_LUNCH_IN = time(12, 30)
F = Path.home() / "long-day-factory.json"
def parse(ts):
return datetime.fromisoformat(ts) if ts else None
def fmt_delta(td):
secs = int(td.total_seconds())
sign = "-" if secs < 0 else ""
secs = abs(secs)
h, m = secs // 3600, (secs % 3600) // 60
return f"{sign}{h}h{m:02d}m"
def main():
flag = sys.argv[1] if len(sys.argv) > 1 else ""
if not F.exists():
print("No ~/long-day-factory.json found. Run long-day-start first.")
sys.exit(3)
data = json.loads(F.read_text())
start = parse(data.get("startTime"))
lunch_out = parse(data.get("lunchTime"))
lunch_in = parse(data.get("backToWork"))
if start is None:
print("startTime is not set. Run long-day-start first.")
sys.exit(3)
now = datetime.now(start.tzinfo)
# Resolve the lunch break.
note = ""
if lunch_out and lunch_in:
lunch_break = lunch_in - lunch_out
if lunch_break.total_seconds() < 0:
lunch_break = timedelta(0)
note = "(backToWork is before lunchTime — treating lunch as 0)"
elif flag == "--default-lunch":
d = start.date()
lunch_out = datetime.combine(d, DEFAULT_LUNCH_OUT, tzinfo=start.tzinfo)
lunch_in = datetime.combine(d, DEFAULT_LUNCH_IN, tzinfo=start.tzinfo)
data["lunchTime"] = lunch_out.isoformat()
data["backToWork"] = lunch_in.isoformat()
F.write_text(json.dumps(data, indent=2) + "\n")
lunch_break = lunch_in - lunch_out
note = "(assumed the standard 11:30-12:30 lunch and saved it)"
elif flag == "--flat-hour":
lunch_break = timedelta(hours=1)
note = "(assumed a flat 1h lunch, not saved)"
else:
missing = []
if not lunch_out:
missing.append("lunchTime")
if not lunch_in:
missing.append("backToWork")
print("NEEDS_LUNCH_DECISION: missing " + ", ".join(missing))
sys.exit(2)
served = (now - start) - lunch_break
remaining = SENTENCE - served
print(f"Clocked in: {start.isoformat()}")
print(f"Lunch break: {fmt_delta(lunch_break)} {note}".rstrip())
print(f"Time served: {fmt_delta(served)}")
if remaining.total_seconds() <= 0:
print("Status: FREE")
print(f"Overtime: {fmt_delta(-remaining)}")
else:
eta = now + remaining
print("Status: NOT FREE")
print(f"Remaining: {fmt_delta(remaining)}")
print(f"Parole at: {eta.strftime('%H:%M')}")
if __name__ == "__main__":
main()
@@ -0,0 +1,30 @@
---
name: back-to-work
description: Log the return from lunch at the Long Day Factory. Records backToWork with the current timestamp in ~/long-day-factory.json. Use when the user says lunch is over / they are back at their desk / "back to work".
---
# back-to-work
Records when the user returns from lunch. The gap between `lunchTime` and
`backToWork` is the lunch break that `am-i-free` subtracts from time served.
## Steps
1. Run:
```bash
bash "$CLAUDE_SKILL_DIR/back.sh"
```
Fallback path: `~/.claude/skills/back-to-work/back.sh`.
2. The script creates `~/long-day-factory.json` if missing and sets `backToWork`
to now.
- If it warns that `lunchTime` is not set, ask the user whether they want to
also set `lunchTime` now (to the current time) or leave it for `am-i-free`
to handle with the default 11:30 assumption. If they say yes, re-run with:
`bash "$CLAUDE_SKILL_DIR/back.sh" --also-lunch`
- If it warns that `startTime` is not set, pass that along.
3. Reply with humor: the machine missed you, the assembly line resumes, etc.
Include the timestamp.
@@ -0,0 +1,27 @@
#!/bin/bash
# Log return from lunch.
set -euo pipefail
F="$HOME/long-day-factory.json"
TS="$(python3 -c 'import datetime;print(datetime.datetime.now().astimezone().isoformat(timespec="seconds"))')"
ALSO_LUNCH="${1:-}"
if [ ! -f "$F" ]; then
printf '{\n "startTime": null,\n "lunchTime": null,\n "backToWork": null\n}\n' > "$F"
fi
tmp="$(mktemp)"
if [ "$ALSO_LUNCH" = "--also-lunch" ]; then
jq --arg ts "$TS" '.backToWork = $ts | (if .lunchTime == null then .lunchTime = $ts else . end)' "$F" > "$tmp" && mv "$tmp" "$F"
echo "Back to work at $TS (also set lunchTime to $TS)"
else
jq --arg ts "$TS" '.backToWork = $ts' "$F" > "$tmp" && mv "$tmp" "$F"
echo "Back to work at $TS"
fi
if [ "$(jq -r '.lunchTime' "$F")" = "null" ]; then
echo "WARNING: lunchTime is not set — ask the user if they want to set it now (--also-lunch)."
fi
if [ "$(jq -r '.startTime' "$F")" = "null" ]; then
echo "WARNING: startTime is not set — did you skip long-day-start this morning?"
fi
Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

@@ -0,0 +1,25 @@
---
name: long-day-start
description: Punch in at the Long Day Factory. Records startTime with the current timestamp in ~/long-day-factory.json and wipes lunchTime / backToWork from any previous shift. Use when the user says they arrived at the office / started their day / "long day start".
---
# long-day-start
Begins a new shift at the Long Day Factory (the office). The sentence is 4 hours,
minus time served at lunch.
## Steps
1. Run the script below. It creates `~/long-day-factory.json` if missing, sets
`startTime` to now (ISO 8601, `-03:00`), and resets `lunchTime` and
`backToWork` to `null`.
```bash
bash "$CLAUDE_SKILL_DIR/start.sh"
```
If `$CLAUDE_SKILL_DIR` is not set, use the absolute path
`~/.claude/skills/long-day-start/start.sh`.
2. Report back to the user with a bit of humor — they've just clocked in and the
clock is now running. Mention the time they punched in.
@@ -0,0 +1,11 @@
#!/bin/bash
# Punch in: set startTime, clear the rest.
set -euo pipefail
F="$HOME/long-day-factory.json"
TS="$(python3 -c 'import datetime;print(datetime.datetime.now().astimezone().isoformat(timespec="seconds"))')"
printf '{\n "startTime": "%s",\n "lunchTime": null,\n "backToWork": null\n}\n' "$TS" > "$F"
echo "Clocked in to the Long Day Factory at $TS"
echo "Wrote $F"
@@ -0,0 +1,26 @@
---
name: lunch-time
description: Log the start of the lunch break at the Long Day Factory. Records lunchTime with the current timestamp in ~/long-day-factory.json. Use when the user says they are going to lunch / "lunch time".
---
# lunch-time
Records when the user leaves for lunch. Lunch is time served — it gets subtracted
from the 4-hour sentence when `am-i-free` does the math.
## Steps
1. Run:
```bash
bash "$CLAUDE_SKILL_DIR/lunch.sh"
```
Fallback path: `~/.claude/skills/lunch-time/lunch.sh`.
2. The script creates `~/long-day-factory.json` if missing and sets `lunchTime`
to now. If it warns that `startTime` is not set, pass that along — the user
may have forgotten to run `long-day-start`.
3. Reply with light humor: bread-and-water break, the parole hearing, etc.
Include the timestamp.
@@ -0,0 +1,19 @@
#!/bin/bash
# Log start of lunch break.
set -euo pipefail
F="$HOME/long-day-factory.json"
TS="$(python3 -c 'import datetime;print(datetime.datetime.now().astimezone().isoformat(timespec="seconds"))')"
if [ ! -f "$F" ]; then
printf '{\n "startTime": null,\n "lunchTime": null,\n "backToWork": null\n}\n' > "$F"
fi
tmp="$(mktemp)"
jq --arg ts "$TS" '.lunchTime = $ts' "$F" > "$tmp" && mv "$tmp" "$F"
echo "Lunch break started at $TS"
if [ "$(jq -r '.startTime' "$F")" = "null" ]; then
echo "WARNING: startTime is not set — did you skip long-day-start this morning?"
fi
@@ -0,0 +1,97 @@
# Tech Demo: Backend Code Reviewer Skill (DSA)
An automated code analysis rule engine designed for enterprise backend systems. This utility intercepts structural anti-patterns, performance bottlenecks, architectural drift, and security hazards within the developer's local CLI or continuous integration workflows (PR Gates). It focuses on universal architectural concepts independent of any single programming language or framework.
---
## Rule Engine & Scope (Expanded & Language-Agnostic)
### 1. Database & Persistence Performance
* **The N+1 Query Problem:** Intercepts database fetch execution inside loop structures (`for`, `foreach`, `while`) caused by missing eager loading, joins, or batching mechanisms (e.g., EF Core, Hibernate, Prisma, TypeORM, SQLAlchemy).
* **Unindexed Queries on Filtered Columns:** Flags queries filtering, joining, or sorting (`WHERE`, `JOIN`, `GROUP BY`, `ORDER BY`) by database columns that do not have an explicitly defined index.
* **Missing Read-Only Optimization (No-Tracking/Read-Replica):** Identifies read-only API endpoints or service queries fetching records without bypassing persistence tracking or memory allocation overhead (e.g., missing `.AsNoTracking()` or not using a read-replica context).
* **Unbounded Result Sets (Missing Pagination):** Flags database queries executing select statements without explicit limits (`LIMIT`, `TAKE`), risking system out-of-memory errors as data grows.
* **In-Memory/Client-Side Evaluation:** Detects queries mapping complex application-layer code or custom functions inside data queries, forcing the application layer to stream the entire table data into memory to perform filtering.
### 2. Concurrency, Async, & Resource Control
* **Dangling / Unawaited Async Executions:** Detects methods declared asynchronous but missing the proper synchronization or orchestration keywords (e.g., missing `await`, `yield`), causing accidental fire-and-forget loops or orphaned threads.
* **Missing Request/Context Propagation (Cancellation Tokens):** Scans execution paths and flags missing propagation of context timers or cancellation tokens down to HTTP clients or database drivers, preventing resource leakage on disconnected client requests.
* **Sync-Over-Async & Thread Blocking:** Catches asynchronous calls forced to run synchronously (e.g., using `.Result`, `.get()`, or blocking execution primitives), risking thread-pool starvation and application deadlocks under load.
### 3. Reliability & Error Resiliency
* **Swallowed & Blind Exceptions:** Flags empty error handling catch blocks (`catch {}`, `except:`) or rethrowing structures that reset the call-stack trace, destroying operational context.
* **Missing Network/Database Retry Policies:** Checks if outbound network requests (HTTP client calls) or core database configurations lack circuit breakers or back-off retry logic to handle transient cloud infrastructure faults.
### 4. Security & Compliance
* **Hardcoded Secrets & Token Entropy:** Scans configuration files (`.json`, `.yml`, `.env`) and application code for hardcoded secrets, connection strings, API private keys, or raw crypto tokens using entropy-based scanner algorithms.
* **Dynamic Command/SQL Injections:** Flags arbitrary execution lines dynamically concatenating external inputs directly into SQL queries, shell arguments, or OS command strings instead of enforcing parameterized boundaries.
### 5. Architectural Boundaries & State
* **Stateful Components in Stateless Environments:** Identifies shared mutable state (e.g., non-thread-safe global variables, in-memory singleton caches) within request scopes, breaking safety guidelines across horizontally scaled instances.
* **Database Migrations Without Structural Rollbacks:** Validates that structural schema migrations require a clear reverse/down fallback script instead of missing routines, allowing deployments to roll back safely during live failures.
* **Domain Entity Leaking (API Layer):** Flags internal data models or database entity classes directly serving as API response data contracts, breaking abstraction barriers and risking unintended data exposure.
---
## Platforms & Pipeline Integrations
### Generic CLI Command (Local Development)
Developers can trigger this utility locally inside any language stack runtime using native container or package binary executors before opening a pull request.
```bash
# Run the agnostic architectural scanner locally against the workspace directory
dsa-reviewer analyze --directory ./src/backend --ruleset standard-backend --fail-on critical
```
### GitHub Actions Workflow (`.github/workflows/backend-review.yml`)
Blocks integration into main branches if any critical rule violation is detected during a Pull Request.
```yaml
name: Universal Backend PR Gate (DSA)
on:
pull_request:
branches: [ main, develop ]
paths:
- 'src/**'
jobs:
review:
name: Architecture & Pattern Analysis
runs-on: ubuntu-latest
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
- name: Install DSA Reviewer CLI
run: curl -sSL https://dsa-reviewer.dev | sh
- name: Execute Pull Request Quality Gates
run: |
dsa-reviewer analyze \
--directory ./src \
--engine rules/backend.json \
--output github-pr-annotations
```
### GitLab CI/CD Pipeline (`.gitlab-ci.yml`)
Integrates natively with GitLab's Code Quality dashboard widget via code-climate formatting report artifacts.
```yaml
stages:
- quality
backend_review_job:
stage: quality
image: dsa/reviewer-engine:latest
only:
- merge_requests
script:
- dsa-reviewer analyze --directory ./src --output codeclimate > gl-code-quality-report.json
artifacts:
name: code-quality-report
expire_in: 1 week
reports:
codequality: gl-code-quality-report.json
```