diff --git a/scripts/verify.mjs b/scripts/verify.mjs index 6b426fe..4ca4f85 100644 --- a/scripts/verify.mjs +++ b/scripts/verify.mjs @@ -48,9 +48,9 @@ for (const token of ['@media(min-width:2200px)','@media(max-width:900px)','@medi console.log('rules standalone verification passed'); for (const token of ['id="catalog"','id="skill-filter"','id="skill-list"','id="detail"','?author=Name&skill=skill-id&view=improved']) if (!reviewHtml.includes(token)) throw new Error(`missing review page content ${token}`); for (const token of ["from './catalog.js'", "from './files.js'",'function renderList','function renderDetail','loadSelectedFile','function selectFromUrl','function syncUrl','URLSearchParams','navigator.clipboard','document.execCommand','download','data-file','searchParams.set(\'file\'']) if (!reviewJs.includes(token)) throw new Error(`missing review interaction ${token}`); -for (const token of ['ndo-repro','reference.md','files =']) if (!reviewFiles.includes(token)) throw new Error(`missing review file manifest ${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'); +for (const token of ['ndo-repro','gfiber-logging','confluence-page','diagram-plantuml','page-reviewer','unslop','reference.md','files =']) if (!`${reviewFiles}\n${read('skills-review/submitted-files.js')}`.includes(token)) throw new Error(`missing review file manifest ${token}`); +if ((reviewCatalog.match(/id:'/g) || []).length + (read('skills-review/submitted-catalog.js').match(/id:'/g) || []).length !== 21) throw new Error('review catalog does not cover all submissions'); +if (!reviewCatalog.includes('hardcoded password') || !reviewCatalog.includes('safety-redacted') || !reviewJs.includes('[REDACTED]') || !reviewJs.includes('[REDACTED LOCAL USER]') || !reviewJs.includes('[REDACTED USER]')) throw new Error('review catalog does not record secret safety handling'); console.log('skills review verification passed'); for (const page of [summaryHtml, modelsHtml, agentsHtml, skillsHtml]) if (!page.includes('../chapters.css') || !page.includes('ROUTE MAP')) throw new Error('chapter page missing shared navigation'); for (const token of ['--ink','@media(max-width:800px)','@media(max-width:520px)']) if (!chaptersCss.includes(token)) throw new Error(`missing chapter responsive contract ${token}`); diff --git a/skill-reviews/improved/confluence-page/SKILL.md b/skill-reviews/improved/confluence-page/SKILL.md new file mode 100644 index 0000000..74489be --- /dev/null +++ b/skill-reviews/improved/confluence-page/SKILL.md @@ -0,0 +1,24 @@ +--- +name: confluence-page +description: Create or update a reviewed Confluence page from a local storage-format draft. Use when the user asks to prepare or publish through an available, approved Confluence connector; require confirmation immediately before publication. +--- + +# confluence-page + +## Inputs +Draft file, target space and title, parent or page ID when applicable, and the requested publication intent. + +## Workflow +1. Check that the configured connector is available and approved. If it is not, prepare the draft and report the exact next step. +2. Create one storage-format draft per page, using a configured draft root or a repository-relative path. +3. Run the package preflight checks; resolve title collisions and compare updates with the current server body. +4. Show the destination, operation, and content summary. Request explicit confirmation for this create or update. +5. Publish only after confirmation, then return the page ID, URL, and version. + +## Rules +- Never include secrets, tokens, PII, or local-machine paths in page content. +- Do not delete pages or attachments. +- Keep the local mirror read-only until the user requests a publication. + +## Output +Return the draft path, validation results, target, confirmation status, and—after publication—the page identifier and URL. diff --git a/skill-reviews/improved/diagram-plantuml/SKILL.md b/skill-reviews/improved/diagram-plantuml/SKILL.md new file mode 100644 index 0000000..6b5bccc --- /dev/null +++ b/skill-reviews/improved/diagram-plantuml/SKILL.md @@ -0,0 +1,23 @@ +--- +name: diagram-plantuml +description: Create a PlantUML diagram and a Confluence storage-format macro for a reviewed page. Use when a user needs a diagram embedded in a supported Confluence page. +--- + +# diagram-plantuml + +## Inputs +The relationship to explain, target page context, and any approved diagram conventions. + +## Workflow +1. Choose a diagram type with `references/diagram-types.md`. +2. Build a small local `.puml` source with a caption and only approved includes. +3. Run a local syntax check when the configured renderer is available. +4. Return the storage macro and state whether syntax and target rendering were independently verified. + +## Rules +- Keep macro markup at the required storage-body level. +- Never load remote or untrusted `!include` sources. +- Do not claim a rendered result without a target-environment preview. + +## Output +Return the diagram source, storage macro, validation result, and any target-environment prerequisite. diff --git a/skill-reviews/improved/gfiber-logging/SKILL.md b/skill-reviews/improved/gfiber-logging/SKILL.md new file mode 100644 index 0000000..0358863 --- /dev/null +++ b/skill-reviews/improved/gfiber-logging/SKILL.md @@ -0,0 +1,24 @@ +--- +name: gfiber-logging +description: Decide and review GFiber service log levels while keeping production INFO output bounded and traceable. Use when adding, changing, or auditing service logs. +--- + +# gfiber-logging + +## Inputs +Changed paths or service root, the request or flow under review, and the project logging configuration. + +## Workflow +1. Read `references/levels.md` to classify each event; use `references/cases.md` for known service patterns. +2. Check new lines for correlation, minimized fields, and bounded volume. +3. Use `references/audit.md` for a static audit; measure representative traffic separately when a path is high-volume. +4. Report each finding with evidence and distinguish measured results from risk estimates. + +## Rules +- Use the approved contextual logger when the project supports one. +- Never log secrets, PII, or full request/response bodies; cap identifier lists. +- Treat INFO caps and DEBUG deployment settings as project configuration facts. Report missing evidence rather than assuming them. +- This skill is read-only. Do not edit code or production configuration. + +## Output +State the scope, each finding (location, level, reason, volume risk, action), audit command/results, and any unmeasured risk. diff --git a/skill-reviews/improved/page-reviewer/SKILL.md b/skill-reviews/improved/page-reviewer/SKILL.md new file mode 100644 index 0000000..54bac48 --- /dev/null +++ b/skill-reviews/improved/page-reviewer/SKILL.md @@ -0,0 +1,22 @@ +--- +name: page-reviewer +description: Review a Confluence-ready draft and its posting context before publication. Use when a user wants an evidence-backed PASS, REVISE, or BLOCK verdict; this skill never publishes or edits a page. +--- + +# page-reviewer + +## Inputs +Draft body, intended space/title/parent, and any available approved connector context. + +## Workflow +1. Run deterministic local checks for content safety, storage structure, links, and diagram markup. +2. If an approved connector is available, check title and target context; otherwise report that check as unavailable. +3. Anchor every finding to a line or section and issue PASS, REVISE, or BLOCK. + +## Rules +- Never publish, edit, or treat placeholders as safe secrets. +- Distinguish allowed internal destinations from unverified hosts using the project policy. +- A missing required validation is a stated limitation, not a pass. + +## Output +Return verdict, scope, findings (severity, anchor, evidence, action), checks run, and the next safe step. diff --git a/skill-reviews/improved/unslop/SKILL.md b/skill-reviews/improved/unslop/SKILL.md new file mode 100644 index 0000000..1b0002c --- /dev/null +++ b/skill-reviews/improved/unslop/SKILL.md @@ -0,0 +1,22 @@ +--- +name: unslop +description: Suggest precise, audience-appropriate revisions for generic or overly polished prose while preserving meaning. Use when a user asks to review a draft’s voice or clarity. +--- + +# unslop + +## Inputs +Draft text, intended audience, and an applicable project style reference when one exists. + +## Workflow +1. Preserve frontmatter, code, XML/HTML, quotations, and technical claims. +2. Identify specific tells using `references/tells.md`; consult the selected style reference before recommending a change. +3. Return small, anchored edits and explain the reader benefit. + +## Rules +- Do not call a dialect, disagreement, or concise writing “slop.” +- Do not rewrite facts, cited wording, or structured content for style. +- Treat scoring thresholds as review aids, not publication gates, unless the project defines them. + +## Output +Return the audience assumption, findings, minimal suggested diffs, preserved sections, and any style-policy uncertainty. diff --git a/skills-review/app.js b/skills-review/app.js index c6f569c..fe127df 100644 --- a/skills-review/app.js +++ b/skills-review/app.js @@ -5,7 +5,7 @@ const state = { selected: catalog[0], query: '', preview: 'original', file: null const $ = (selector) => document.querySelector(selector); const escape = (value) => value.replace(/[&<>"']/g, (character) => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' })[character]); const redact = (value, entry) => { - const safe = value.replace(/(NDO_PASS[^\n=]*[=:]\s*["']?)[^\n"']+/gi, '$1[REDACTED]').replace(/(password["']?\s*[:=]\s*["']?)[^\n"']+/gi, '$1[REDACTED]').replace(/\b[\w.+-]+@[\w.-]+\.[a-z]{2,}\b/gi, '[REDACTED SERVICE ACCOUNT]'); + const safe = value.replace(/(NDO_PASS[^\n=]*[=:]\s*["']?)[^\n"']+/gi, '$1[REDACTED]').replace(/(password["']?\s*[:=]\s*["']?)[^\n"']+/gi, '$1[REDACTED]').replace(/\b[\w.+-]+@[\w.-]+\.[a-z]{2,}\b/gi, '[REDACTED SERVICE ACCOUNT]').replace(/\/home\/[A-Za-z0-9._-]+(?=\/)/g, '[REDACTED LOCAL USER]').replace(/display\/~[A-Za-z0-9._-]+/gi, 'display/~[REDACTED USER]'); return entry.id === 'ndo-repro' ? safe.replace(/https?:\/\/[^\s)>]+/gi, '[REDACTED URL]').replace(/\b(?:[\w-]+\.)*netcracker\.[\w.-]+\b/gi, '[REDACTED HOST]').replace(/\bpedro[._ -]?aranha\b/gi, '[REDACTED CONTRIBUTOR]') : safe; }; 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); }; diff --git a/skills-review/catalog.js b/skills-review/catalog.js index 4350742..2d2ec54 100644 --- a/skills-review/catalog.js +++ b/skills-review/catalog.js @@ -1,3 +1,5 @@ +import { newSubmissions } from './submitted-catalog.js'; + export const sources = { specification: 'https://agentskills.io/specification', practices: 'https://agentskills.io/skill-creation/best-practices', @@ -8,7 +10,7 @@ export const sources = { const skill = (name, description, body) => `---\nname: ${name}\ndescription: ${description}\n---\n\n# ${name}\n\n${body.trim()}\n`; -export const catalog = [ +const originalCatalog = [ { 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 repository’s 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 project’s 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.`)}, @@ -26,3 +28,5 @@ export const catalog = [ { 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 project’s 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.`)} ]; + +export const catalog = [...originalCatalog, ...newSubmissions]; diff --git a/skills-review/files.js b/skills-review/files.js index de2fff5..261fc08 100644 --- a/skills-review/files.js +++ b/skills-review/files.js @@ -1,4 +1,6 @@ -export const files = { +import { newSubmissionFiles } from './submitted-files.js'; + +const originalFiles = { 'angular-accessibility-root': [{ name:'SKILL.md', path:'../submitted-skills/Leonardo%20Uno/SKILL.md', kind:'skill' }, { name:'skills/angular-accessibility/SKILL.md', path:'../submitted-skills/Leonardo%20Uno/skills/angular-accessibility/SKILL.md', kind:'skill' }], 'copy-quote-info-to-payload': [{ name:'SKILL.md', path:'../submitted-skills/Lucas%20Mantovan/skills/copy-quote-info-to-payload/SKILL.md', kind:'skill' }, { name:'reference.md', path:'../submitted-skills/Lucas%20Mantovan/skills/copy-quote-info-to-payload/reference.md', kind:'reference' }], 'ndo-repro': [{ name:'SKILL.md', path:'../submitted-skills/Anonymous%20Operational%20Submission/skills/ndo-repro/SKILL.md', kind:'skill' }, { name:'envs.tsv', path:'../submitted-skills/Anonymous%20Operational%20Submission/skills/ndo-repro/envs.tsv', kind:'data' }, { name:'lib/env.sh', path:'../submitted-skills/Anonymous%20Operational%20Submission/skills/ndo-repro/lib/env.sh', kind:'script' }, { name:'ndo-api.sh', path:'../submitted-skills/Anonymous%20Operational%20Submission/skills/ndo-repro/ndo-api.sh', kind:'script' }, { name:'ndo-ship.sh', path:'../submitted-skills/Anonymous%20Operational%20Submission/skills/ndo-repro/ndo-ship.sh', kind:'script' }, { name:'reference/bom-Dockerfile_local.example', path:'../submitted-skills/Anonymous%20Operational%20Submission/skills/ndo-repro/reference/bom-Dockerfile_local.example', kind:'reference' }, { name:'reference/dockerfile-local.md', path:'../submitted-skills/Anonymous%20Operational%20Submission/skills/ndo-repro/reference/dockerfile-local.md', kind:'reference' }], @@ -7,3 +9,5 @@ export const files = { 'long-day-start': [{ name:'SKILL.md', path:'../submitted-skills/Vinicius%20Nascimento/skills/long-day-start/SKILL.md', kind:'skill' }, { name:'start.sh', path:'../submitted-skills/Vinicius%20Nascimento/skills/long-day-start/start.sh', kind:'script' }], 'lunch-time': [{ name:'SKILL.md', path:'../submitted-skills/Vinicius%20Nascimento/skills/lunch-time/SKILL.md', kind:'skill' }, { name:'lunch.sh', path:'../submitted-skills/Vinicius%20Nascimento/skills/lunch-time/lunch.sh', kind:'script' }] }; + +export const files = { ...originalFiles, ...newSubmissionFiles }; diff --git a/skills-review/submitted-catalog.js b/skills-review/submitted-catalog.js new file mode 100644 index 0000000..2a15643 --- /dev/null +++ b/skills-review/submitted-catalog.js @@ -0,0 +1,9 @@ +const skill = (name, description, body) => `---\nname: ${name}\ndescription: ${description}\n---\n\n# ${name}\n\n${body.trim()}\n`; + +export const newSubmissions = [ + { id:'gfiber-logging', author:'Gustavo Ruiz', path:'../submitted-skills/Gustavo%20Ruiz/skills/gfiber-logging/SKILL.md', title:'GFiber logging', status:'Strong policy package', focus:'Choose and audit production log levels while keeping INFO volume bounded.', wins:['Excellent level-selection rules, practical cases, and a volume-audit workflow.','Clear data-minimization and correlation guidance.','Supporting references make the policy easy to apply.'], improve:['Make environment-specific assertions, such as DEBUG availability, configurable facts with evidence from the deployed project.','Package the audit heuristic as a versioned script with fixtures instead of leaving it only in prose.','Add a stable review output contract: location, proposed level, reason, volume risk, and measurement evidence.'], extras:'Add an evaluation fixture for a hot loop, a payload dump, and a correctly bounded per-item result line.', improved:skill('gfiber-logging','Decide and review GFiber service log levels while keeping production INFO output bounded and traceable. Use when adding, changing, or auditing service logs.',`## Inputs\nChanged paths or service root, the request or flow under review, and the project logging configuration.\n\n## Workflow\n1. Read \`references/levels.md\` to classify each event; use \`references/cases.md\` for known service patterns.\n2. Check new lines for correlation, minimized fields, and bounded volume.\n3. Use \`references/audit.md\` for a static audit; measure representative traffic separately when a path is high-volume.\n4. Report each finding with evidence and distinguish measured results from risk estimates.\n\n## Rules\n- Use the approved contextual logger when the project supports one.\n- Never log secrets, PII, or full request/response bodies; cap identifier lists.\n- Treat INFO caps and DEBUG deployment settings as project configuration facts. Report missing evidence rather than assuming them.\n- This skill is read-only. Do not edit code or production configuration.\n\n## Output\nState the scope, each finding (location, level, reason, volume risk, action), audit command/results, and any unmeasured risk.`)}, + { id:'confluence-page', author:'Marcos Silva', path:'../submitted-skills/Marcos%20Silva/skills/confluence-page/SKILL.md', title:'Confluence page', status:'Strong publishing workflow', focus:'Prepare and publish reviewed Confluence storage-format pages through an approved connector.', wins:['Detailed storage-format guidance, templates, preflight scripts, and attachment rules.','Safeguards around drafts, title collisions, and server-side diffs are thoughtful.'], improve:['Require explicit user confirmation immediately before every create or update action.','Replace user-specific local paths with a configured draft root or repository-relative paths.','Treat connector availability and approval as runtime checks, not assumptions.'], extras:'Add test fixtures for title collisions, unavailable connectors, unsafe content, and a failed PlantUML check.', improved:skill('confluence-page','Create or update a reviewed Confluence page from a local storage-format draft. Use when the user asks to prepare or publish through an available, approved Confluence connector; require confirmation immediately before publication.',`## Inputs\nDraft file, target space and title, parent or page ID when applicable, and the requested publication intent.\n\n## Workflow\n1. Check that the configured connector is available and approved. If it is not, prepare the draft and report the exact next step.\n2. Create one storage-format draft per page, using a configured draft root or a repository-relative path.\n3. Run the package preflight checks; resolve title collisions and compare updates with the current server body.\n4. Show the destination, operation, and content summary. Request explicit confirmation for this create or update.\n5. Publish only after confirmation, then return the page ID, URL, and version.\n\n## Rules\n- Never include secrets, tokens, PII, or local-machine paths in page content.\n- Do not delete pages or attachments.\n- Keep the local mirror read-only until the user requests a publication.\n\n## Output\nReturn the draft path, validation results, target, confirmation status, and—after publication—the page identifier and URL.`)}, + { id:'diagram-plantuml', author:'Marcos Silva', path:'../submitted-skills/Marcos%20Silva/skills/diagram-plantuml/SKILL.md', title:'PlantUML diagram', status:'Useful focused helper', focus:'Produce a valid PlantUML diagram and Confluence storage macro for a reviewed page.', wins:['Focused macro guidance and useful diagram-type and troubleshooting references.','Optional local syntax check is a sensible quality gate.'], improve:['Do not imply that a Confluence macro is installed or renders without checking the target environment.','Allow only approved, bundled includes; do not fetch untrusted includes at render time.','Report syntax validation separately from a confirmed rendered preview.'], extras:'Add fixtures for malformed diagrams, missing macro support, and approved standard-library includes.', improved:skill('diagram-plantuml','Create a PlantUML diagram and a Confluence storage-format macro for a reviewed page. Use when a user needs a diagram embedded in a supported Confluence page.',`## Inputs\nThe relationship to explain, target page context, and any approved diagram conventions.\n\n## Workflow\n1. Choose a diagram type with \`references/diagram-types.md\`.\n2. Build a small local \`.puml\` source with a caption and only approved includes.\n3. Run a local syntax check when the configured renderer is available.\n4. Return the storage macro and state whether syntax and target rendering were independently verified.\n\n## Rules\n- Keep macro markup at the required storage-body level.\n- Never load remote or untrusted \`!include\` sources.\n- Do not claim a rendered result without a target-environment preview.\n\n## Output\nReturn the diagram source, storage macro, validation result, and any target-environment prerequisite.`)}, + { id:'page-reviewer', author:'Marcos Silva', path:'../submitted-skills/Marcos%20Silva/skills/page-reviewer/SKILL.md', title:'Page reviewer', status:'Strong non-mutating gate', focus:'Review a Confluence draft before publishing and provide an evidence-backed verdict.', wins:['Clear PASS / REVISE / BLOCK model with anchored findings.','Non-mutating scope and optional PlantUML checks are well defined.'], improve:['Make connector-dependent checks conditional and state the fallback when the connector is unavailable.','Clarify which internal links and hostnames are permitted instead of using a broad suffix exception.','Add deterministic fixtures for secrets, title collisions, and invalid macros.'], extras:'Publish a compact machine-readable finding schema so the dry-run script and human review agree.', improved:skill('page-reviewer','Review a Confluence-ready draft and its posting context before publication. Use when a user wants an evidence-backed PASS, REVISE, or BLOCK verdict; this skill never publishes or edits a page.',`## Inputs\nDraft body, intended space/title/parent, and any available approved connector context.\n\n## Workflow\n1. Run deterministic local checks for content safety, storage structure, links, and diagram markup.\n2. If an approved connector is available, check title and target context; otherwise report that check as unavailable.\n3. Anchor every finding to a line or section and issue PASS, REVISE, or BLOCK.\n\n## Rules\n- Never publish, edit, or treat placeholders as safe secrets.\n- Distinguish allowed internal destinations from unverified hosts using the project policy.\n- A missing required validation is a stated limitation, not a pass.\n\n## Output\nReturn verdict, scope, findings (severity, anchor, evidence, action), checks run, and the next safe step.`)}, + { id:'unslop', author:'Marcos Silva', path:'../submitted-skills/Marcos%20Silva/skills/unslop/SKILL.md', title:'Unslop', status:'Thoughtful style review', focus:'Identify generic, overly polished language and suggest precise revisions without changing meaning.', wins:['Useful tell list and a deliberately non-destructive review orientation.','References acknowledge context and audience concerns.'], improve:['Make audience and project style an explicit input rather than a universal house voice.','Treat score thresholds as calibrated defaults supported by evaluation examples, not fixed truth.','Protect quotations, code, structured markup, and technical claims from stylistic rewriting.'], extras:'Add labeled before/after fixtures from several document types and measure reviewer agreement.', improved:skill('unslop','Suggest precise, audience-appropriate revisions for generic or overly polished prose while preserving meaning. Use when a user asks to review a draft’s voice or clarity.',`## Inputs\nDraft text, intended audience, and an applicable project style reference when one exists.\n\n## Workflow\n1. Preserve frontmatter, code, XML/HTML, quotations, and technical claims.\n2. Identify specific tells using \`references/tells.md\`; consult the selected style reference before recommending a change.\n3. Return small, anchored edits and explain the reader benefit.\n\n## Rules\n- Do not call a dialect, disagreement, or concise writing “slop.”\n- Do not rewrite facts, cited wording, or structured content for style.\n- Treat scoring thresholds as review aids, not publication gates, unless the project defines them.\n\n## Output\nReturn the audience assumption, findings, minimal suggested diffs, preserved sections, and any style-policy uncertainty.`)} +]; diff --git a/skills-review/submitted-files.js b/skills-review/submitted-files.js new file mode 100644 index 0000000..ff549f4 --- /dev/null +++ b/skills-review/submitted-files.js @@ -0,0 +1,29 @@ +const gustavo = '../submitted-skills/Gustavo%20Ruiz/skills/'; +const marcos = '../submitted-skills/Marcos%20Silva/'; + +export const newSubmissionFiles = { + 'gfiber-logging': [ + { name:'SKILL.md', path:`${gustavo}gfiber-logging/SKILL.md`, kind:'skill' }, + { name:'confluence-page-source.txt', path:`${gustavo}confluence-page-source.txt`, kind:'reference' }, + ...['anti-patterns.md','audit.md','cases.md','levels.md'].map(name => ({ name:`references/${name}`, path:`${gustavo}gfiber-logging/references/${name}`, kind:'reference' })) + ], + 'confluence-page': [ + { name:'SKILL.md', path:`${marcos}skills/confluence-page/SKILL.md`, kind:'skill' }, + ...['attachments.md','macros.md','secrets.md','space-keys.md'].map(name => ({ name:`references/${name}`, path:`${marcos}skills/confluence-page/references/${name}`, kind:'reference' })), + ...['check-mcp-atlassian.sh','dry-run-publish.sh','new-page.sh'].map(name => ({ name:`scripts/${name}`, path:`${marcos}scripts/${name}`, kind:'script' })), + ...['how-to.md','hub-page.md','postmortem.md','rfc.md'].map(name => ({ name:`templates/${name}`, path:`${marcos}templates/${name}`, kind:'template' })), + { name:'README.md', path:`${marcos}README.md`, kind:'reference' } + ], + 'diagram-plantuml': [ + { name:'SKILL.md', path:`${marcos}skills/diagram-plantuml/SKILL.md`, kind:'skill' }, + ...['diagram-types.md','troubleshooting.md'].map(name => ({ name:`references/${name}`, path:`${marcos}skills/diagram-plantuml/references/${name}`, kind:'reference' })) + ], + 'page-reviewer': [ + { name:'SKILL.md', path:`${marcos}skills/page-reviewer/SKILL.md`, kind:'skill' }, + { name:'references/checks.md', path:`${marcos}skills/page-reviewer/references/checks.md`, kind:'reference' } + ], + 'unslop': [ + { name:'SKILL.md', path:`${marcos}skills/unslop/SKILL.md`, kind:'skill' }, + ...['house-style.md','tells.md'].map(name => ({ name:`references/${name}`, path:`${marcos}skills/unslop/references/${name}`, kind:'reference' })) + ] +}; diff --git a/submitted-skills/Gustavo Ruiz/skills/confluence-page-source.txt b/submitted-skills/Gustavo Ruiz/skills/confluence-page-source.txt new file mode 100644 index 0000000..6e88ee2 --- /dev/null +++ b/submitted-skills/Gustavo Ruiz/skills/confluence-page-source.txt @@ -0,0 +1,229 @@ +h2. Overview + +Which level to use for a log line in GFiber services. + +Graylog storage is shared, so every INFO line written on a healthy run is paid for in retention days: the more a service logs, the shorter the window for grepping an incident that already happened. A service that logs too little is untriageable. This page is the line between the two. + +Applies to all GFiber services. The 13 Go services log through {{mano.netcracker.com/go-logging/v3}}; the Java services follow the same levels with different API names. + +Three things to know before choosing a level: + +* {{LOG_LEVEL}} is {{INFO}} in every shipped Helm chart. Treat DEBUG as *not present in production*. +* Support starts from one identifier, usually an alarm id or a ticket id, and searches Graylog full text. A decision that never printed that identifier cannot be found. +* Batch sizes are not capped upstream. A line inside a loop scales with ONT or item count, not with request count. + +h2. Levels + +|| Level || Use for || Volume on a healthy run || +| ERROR | Work was lost and a human must look. Carries the identifiers of the lost work. | rare, each one actionable | +| WARN | An item was dropped or degraded and the service continues. Carries identifiers when no result line will be written. | rare | +| INFO | Work received, work finished, one result per work item. | O(1) per request or batch, plus one line per item | +| DEBUG | Everything else: intermediate collections, per-object detail, payloads, filter internals. | unbounded | +| FATAL | Cannot start and serve. Terminates the process. | startup only | + +h2. How to choose + +Stop at the first yes. + +# Work was lost and someone has to look at it. → *ERROR* +# An item was dropped or degraded, and the service keeps going. → *WARN* +# It is one of these four: work received, work finished, the result of one item, or a decision that ends an item and is not already in that item's result message. → *INFO* +# It fires more than once per item, or prints a collection, a struct or a body. → *DEBUG* +# Anything else. → *DEBUG* + +{tip} +Unsure between two levels? Take the lower one. A line at DEBUG can be recovered with on-demand troubleshooting or promoted next release. Retention days spent on a line nobody reads cannot. +{tip} + +h3. WARN or ERROR + +The boundary that gets argued about most. + +* *ERROR* means the service could not do what it was asked and no automatic mechanism will fix it. A human has to look. +* *WARN* means the service did not do something, but that outcome is defined and expected in operation: input was unusable, capacity was full, a business rule dropped the item. + +The test: *if this fires two hundred times tonight, does someone need to be paged?* Yes is ERROR. No is WARN. + +Two consequences worth stating, because both are commonly got wrong: + +* A call that failed but *will be retried automatically* is not an ERROR on the attempt. The attempt is DEBUG. It becomes ERROR when the retries are exhausted and the work is actually lost. +* A validation rejection is never an ERROR, however loud it looks. The client sent something unusable and the service behaved correctly. That is WARN. + +h3. FATAL + +Startup only, and only when the process cannot serve at all: unreadable configuration, no database, a required dependency that will never appear. {{LogFatal}} terminates the process, so calling it on a request path turns one bad request into an outage. There is no case for FATAL after the service reports ready. + +h2. Cases + +h3. Work intake and results + +|| Case || Level || Note || +| Request, batch or message arrived | INFO | counts and the values that identify the scope, such as alarm names, severities, OLT, HUT; no payload and no id list | +| Batch finished | INFO if ok, ERROR otherwise | one summary line with in, out, duration and status, written from a defer registered before any recover so a panic still produces it | +| Result of one work item | INFO | one per item, with its identifier and outcome; this is the line support greps for, and the one line that must never be demoted | +| Payload of the work item | DEBUG | or behind on-demand troubleshooting | +| Decision that ends the item | INFO | only when it is not already visible in that item's result message | +| Intermediate lookup or filter result | DEBUG | log the count at INFO if it matters, the members at DEBUG | +| Anything inside a loop over domain objects | DEBUG | plus one count after the loop | + +h3. Rejections and failures + +|| Case || Level || Note || +| Input malformed, null or failed validation | WARN | carry the identifiers that survived parsing, and the body size | +| Rejected for capacity or backpressure | WARN | one line per rejected request, never per item | +| No handler or policy matched the work | WARN | carry the identifiers, because no result line will be written | +| Upstream call failed, will be retried | DEBUG | the attempt is not yet a failure | +| Upstream call failed after retries | ERROR | carry the identifiers and the step that stopped | +| Some items succeeded, some failed | ERROR | on the summary line, with the split | +| Panic recovered | ERROR | log the recovered value and the stack, and keep serving | + +h3. Service lifecycle + +|| Case || Level || Note || +| Started, listeners bound, dependencies resolved | INFO | a handful of lines, once per process | +| Effective configuration | DEBUG | never secrets, tokens or credentials | +| Graceful shutdown | INFO | | +| Cannot start at all | FATAL | the only place FATAL is allowed | +| Database connection established | INFO | once at startup; per query is DEBUG | + +h3. Background work + +|| Case || Level || Note || +| Scheduled tick that found nothing to do | DEBUG | a tick every few seconds at INFO is one of the cheapest ways to burn retention | +| Scheduled tick that did work | INFO | one line with counts, not one per item | +| Kafka batch consumed | INFO | one summary per batch, same shape as an HTTP batch | +| One Kafka message processed | DEBUG | the per-item result line already covers what support needs | +| Message that cannot be parsed | ERROR | carry the message key and raise a metric; it will never parse, so it is lost work | +| Consumer rebalance or lag | none | leave it to the client library and to metrics | + +h3. Keep out + +|| Case || Level || Note || +| Health, liveness and readiness probes | none on success | probe traffic is constant; log only a failing probe | +| Every outbound HTTP request and response | DEBUG | rates and durations belong in metrics | +| Upstream returned an empty result | DEBUG | unless it changes the outcome, and then it belongs in the item's result message | +| Third-party library output | set it explicitly | do not let a dependency inherit DEBUG in production | +| Secrets, tokens, passwords | never | at any level | +| ONT serial, account id, hostname | not at INFO | on high-volume paths; fine in a bounded projection or at DEBUG | + +If a line has to be INFO and is still too frequent, *sample it*: log one in N with the count of what was skipped. Demoting it to DEBUG removes it from production entirely, which is usually not the intent. + +h2. Rules + +# No unbounded collection at INFO. The count belongs at INFO, the collection behind it at DEBUG. +# No INFO inside a loop over domain objects. +# Cap identifier lists at 50 entries followed by {{+N more}}. +# Always use the {{Ctx}} variant. {{LogInfo}} without {{Ctx}} drops {{request_id}} and every business identifier from the MDC, which makes the line impossible to attach to anything. +# Never log a full request or response body at INFO. +# Mint correlation ids at ingress, not deeper. An id created inside the handler that already needed it cannot join the lines written before that point. +# No secrets, tokens or customer PII at any level. + +These double as the review checklist. Ask them on any MR that adds or moves a log line. + +h2. Field format + +{{key=value}} pairs, snake_case keys, prefixed by the subject of the line. Quote with {{%q}} only when the value can be empty or contain spaces. + +{code:go} +logging.LogInfoCtx(ctx, "policy batch received: batch_id=%s policy=%q alarms=%d alarm_names=%s", + batchID, request.Policy, len(request.Alarms), distinctAlarmNames(request.Alarms)) +{code} + +The runtime already adds a prefix, so do not repeat any of it in the message: + +{noformat} +[2026-09-02T11:52:06.222] [INFO] [request_id=-] [tenant_id=-] [thread=-] [class=policies:executor.go:68] +{noformat} + +|| Key || Source || Present on || +| request_id | MDC, from the cloud-core context propagation middleware | every line, automatically | +| batch_id | minted once at ingress, carried in the context | every line handling that batch | +| alarm_id, ticket_id, order_id | the domain object | every line naming a single work item | +| alarm_ids | capped list | lines describing a set | + +{note} +This is not structured logging. The logger emits a text message behind a fixed prefix, so Graylog does not extract these keys into searchable fields. They are found by full text search, which is exactly why identifiers have to appear literally in the message. +{note} + +h2. Anti-patterns + +All of these shipped and passed review. + +h3. Printing a pointer instead of the data + +{code:go} +logging.LogInfoCtx(ctx, "Valid alarms: %+v", validAlarms) // map[string]*Alarm +{code} + +Go's {{fmt}} does not dereference pointers held inside a map or a slice, so what reaches Graylog is a map key and a heap address: + +{noformat} +Valid alarms: map[7c0e-1:0x7cabe66aa060] +{noformat} + +Print the identifiers, or a count. + +h3. A verb that is not a verb + +{code:go} +logging.LogDebug("... for alarm %s+", alarm) // *Alarm +{code} + +{{%s+}} is {{%s}} followed by a literal plus. On a struct with non-string fields {{%s}} emits error markers: + +{noformat} +&{7c0e-1 %!s(int=3) %!s(bool=false) 2026-09-02 11:52:06 ...}+ +{noformat} + +h3. INFO inside a per-object loop + +{code:go} +for _, target := range targets { + ... + logging.LogInfo("ONT target %s is not eligible for this ticket: %+v", ontId, target) +} +{code} + +One INFO line per monitoring target, dumping the whole struct, where the logged branch is the *normal* outcome and not an exception. This scales with ONT count, not with request count. Log the members at DEBUG and one count after the loop. + +h3. A rejection that returns in silence + +A request rejected for capacity, for an unmatched handler or for a malformed body, returning a status code with no log line and no metric. Every identifier in that request is then absent from Graylog, and the request counter and the result counter diverge with nothing to explain the gap. + +h3. Losing the panic value + +{code:go} +logging.LogErrorCtx(ctx, "Unexpected panic: %v", reasonConstant, stackTrace) +{code} + +One verb, two arguments. The recovered value is never printed and the stack trace arrives as {{%!(EXTRA string=...)}}. + +h2. On-demand extended logging + +How a service gets full detail in production without raising {{LOG_LEVEL}} and without paying for it on every healthy run. Every service handling a high-volume work item should implement it. {{gfiber-policy-executor}} is the reference: + +{noformat} +PUT /troubleshooting/{entityKey}?minutes=1440 +DELETE /troubleshooting/{entityKey} +GET /troubleshooting/{entityKey} +{noformat} + +In code it is a guard around the verbose block, so the cost when off is one cached lookup: + +{code:go} +logging.LogInfoCtx(ctx, "Handling Full Pon Loss for alarm: %+v", alarm.toShortString()) +if m.IsAlarmTroubleshootingActive(ctx, alarm) { + logging.LogInfoCtx(ctx, "Alarm (full): %+v", alarm.toFullString()) +} +{code} + +The default line carries a bounded projection; the full payload is behind the guard. Setup and the supported entity keys: [How to enable troubleshooting logs [gfiber-policy-executor]|https://bass.netcracker.com/pages/viewpage.action?pageId=2466165241]. + +h2. Logs are not the only channel + +Choosing the right channel is most of the volume problem. A line that belongs in a metric should not be a log. + +|| Channel || Answers || Cannot || +| Service log (Graylog) | what happened to this specific id | show trends, and it costs shared retention | +| Prometheus metric | how often, how slow, alerting | carry an identifier; label cardinality forbids it | +| BLM policy_actions_log | what we did to this item, on the record | be found from the SA Graylog streams | diff --git a/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/SKILL.md b/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/SKILL.md new file mode 100644 index 0000000..32324a4 --- /dev/null +++ b/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/SKILL.md @@ -0,0 +1,83 @@ +--- +name: gfiber-logging +description: >- + Decides the level of a log line in GFiber services and keeps INFO volume bounded. + Use when writing or reviewing logging code, choosing between DEBUG, INFO, WARN and + ERROR, adding observability to a service, judging whether a line belongs in a log or + a metric, or auditing a service for log volume before a merge request. +--- + +# GFiber Logging + +Level policy and field conventions for log lines in GFiber services. + +Canonical source: [How To: What logs belong at INFO, DEBUG, WARN and ERROR in GFiber services](https://bass.netcracker.com/display/GF/How+To%3A++What+logs+belongs+at+INFO%2C+DEBUG%2C+WARN+and+ERROR+in+GFiber+services). When this skill and the BASS page disagree, the page wins and this skill gets updated. + +References: [references/levels.md](references/levels.md), [references/cases.md](references/cases.md), [references/anti-patterns.md](references/anti-patterns.md), [references/audit.md](references/audit.md). + +## Hard rules + +- **INFO is capped** — work received, work finished, one result per work item. Nothing else. +- **No unbounded collection at INFO** — the count is INFO, the collection behind it is DEBUG. +- **No INFO inside a loop** over alarms, ONTs, targets, services, tickets or messages. The per-item result line is the one legitimate exception. +- **Cap identifier lists** at 50 entries followed by `+N more`. +- **Always the `Ctx` variant** — `LogInfoCtx`, never `LogInfo`. The plain call drops `request_id` and every business identifier. +- **Never a full request or response body at INFO** — log a projection; bodies go to DEBUG or behind on-demand troubleshooting. +- **Mint correlation ids at ingress**, not deeper. An id created inside the handler cannot join the lines written before it. +- **No secrets, tokens or customer PII** at any level. +- **DEBUG is not present in production** — `LOG_LEVEL` is `INFO` in every shipped chart. A decision that must be explainable in production cannot live at DEBUG. + +## Workflow: one log line + +1. Walk the decision list in [references/levels.md](references/levels.md) and stop at the first yes. +2. If the answer was INFO, confirm the line matches one of the four INFO cases. If it does not, it is DEBUG. +3. Look the situation up in [references/cases.md](references/cases.md). Startup, scheduled ticks, Kafka, health probes and upstream calls all have a fixed answer there. +4. Apply the field format from [references/levels.md](references/levels.md): `key=value`, snake_case, subject prefix, `%q` only for values that can be empty or contain spaces. +5. Confirm the identifiers. On WARN and ERROR, add them only where no per-item result line will run for that work. + +## Workflow: adding logging to a service + +1. Read [references/cases.md](references/cases.md) and pick the reference implementation closest to the service shape (request handler, batch policy, scheduler, Kafka consumer). +2. Run the static audit in [references/audit.md](references/audit.md) to record the starting numbers. +3. Add the three INFO lines the policy expects, in this order, because each one is useless without the previous: work received, per-item result, batch summary. +4. Add WARN on every branch that rejects or drops work, with a fixed reason vocabulary and a counter. +5. Add ERROR on every branch that loses work after retries, carrying the identifiers and the step that stopped. +6. Demote or delete what the audit flagged: collection dumps, per-object INFO, ticks that fire on a timer, lines whose whole content is already in the runtime prefix. +7. Re-run the audit and report before and after. + +## Workflow: reviewing a merge request + +1. Apply the checklist in [references/audit.md](references/audit.md). +2. Check the level of each added line against [references/cases.md](references/cases.md), not against how important the code feels. +3. Scan for the known anti-patterns in [references/anti-patterns.md](references/anti-patterns.md). Pointer maps, bad verbs and silent rejections are the three that recur. +4. If the change touches a high-volume path, require the volume gate table in the merge request description. + +## Workflow: auditing a service for volume + +1. Run the static audit script from [references/audit.md](references/audit.md) at the service checkout root. +2. Exclude lines already behind an on-demand troubleshooting guard; the ungated count is the one that matters. +3. Rank by `dump` and `loop` rather than by raw INFO count: a service with few INFO lines that all print collections is worse than one with many bounded lines. +4. Measure the real numbers on a reference scenario per the volume gate, not only the static count. + +## Choosing the channel + +Most of the volume problem is picking the wrong channel. Full table in [references/levels.md](references/levels.md). + +- "How often" or "how slow" is a **metric**, and it cannot carry an identifier. +- "What happened to this specific id" is a **log**, and it costs shared retention. +- "What did we do to this item, on the record" is a **BLM action log**, and it is not reachable from the SA Graylog streams. + +## Safety + +- **Read-only** — this skill reasons about code and proposes changes. It runs no mutation of its own. +- Source trees under `sources/product/` are read-only; propose changes, never edit. +- Sync sources with `gfiber-sources` before auditing a service. + +## Related skills + +| Skill | Role | +|-------|------| +| `gfiber-sources` | Clone or checkout the service before auditing it | +| `gfiber-sa-troubleshooting` | Consumer of these logs; its Graylog searches are why identifiers must be literal | +| `gfiber-svt-analysis` | Registered SVT cases used as the reference scenario for the volume gate | +| `skills/_shared/code-reviewer` | General review pass; this skill covers the logging dimension only | diff --git a/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/references/anti-patterns.md b/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/references/anti-patterns.md new file mode 100644 index 0000000..8d1e420 --- /dev/null +++ b/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/references/anti-patterns.md @@ -0,0 +1,88 @@ +# Anti-patterns + +Every example below shipped and passed review in a GFiber service. Check for these first when auditing. + +## Printing a pointer instead of the data + +```go +logging.LogInfoCtx(ctx, "Valid alarms: %+v", validAlarms) // map[string]*Alarm +logging.LogInfoCtx(ctx, "Alarm results: %+v", alarmResults) // map[string]*AlarmResult +``` + +Go's `fmt` does not dereference pointers held inside a map or a slice, so what reaches Graylog is a map key and a heap address: + +``` +Valid alarms: map[7c0e-1:0x7cabe66aa060] +Alarm results: map[7c0e-1:0x7cabe66b4000] +``` + +Print the identifiers, or a count. A struct or map of values prints fine; a map or slice of pointers does not. + +## A verb that is not a verb + +```go +logging.LogDebug("... for alarm %s+", alarm) // *Alarm +``` + +`%s+` is `%s` followed by a literal plus. On a struct with non-string fields `%s` emits error markers: + +``` +&{7c0e-1 %!s(int=3) %!s(bool=false) 2026-09-02 11:52:06 ...}+ +``` + +Use `%+v`, or a short projection method such as `toShortString()`. + +## INFO inside a per-object loop + +```go +for _, target := range targets { + ... + logging.LogInfo("ONT target %s is not eligible for this ticket: %+v", ontId, target) +} +``` + +One INFO line per monitoring target, dumping the whole struct, where the logged branch is the normal outcome and not an exception. This scales with ONT count, not with request count. Log the members at DEBUG and one count after the loop. + +## A tick that logs whether or not there is work + +```go +logging.LogInfoCtx(ctx, "Schedule ticket updates at %v", time.Now()) +``` + +Fired on every scheduler tick. With a five second interval that is roughly 17k INFO lines per day per pod with no work behind them. The tick belongs at DEBUG; the INFO line belongs after the batch, with counts. + +## A rejection that returns in silence + +A request rejected for capacity, for an unmatched handler or for a malformed body, returning a status code with no log line and no metric. Every identifier in that request is then absent from Graylog, and the request counter and the result counter diverge with nothing to explain the gap. + +## A result line that never runs + +An early return on a failure path that skips the per-item result loop. The batch is lost and leaves one line with no identifier in it. Populate the results on every exit path, or carry the identifiers on the ERROR. + +Watch the status code when fixing this: in `gfiber-policy-executor` filling the results made a fully failed batch fall through the handler condition and answer HTTP 200, and the caller only inspects the status code, so it would have marked the work completed. + +## Losing the panic value + +```go +logging.LogErrorCtx(ctx, "Unexpected panic: %v", reasonConstant, stackTrace) +``` + +One verb, two arguments. The recovered value is never printed and the stack trace arrives as `%!(EXTRA string=...)`. + +## A line whose whole content is already in the prefix + +```go +logging.LogInfoCtx(ctx, "x-request-id=%s", requestId) +``` + +The runtime prefix already carries `request_id`. The line names no work item, so it costs volume and answers nothing. Replace it with a work-received line that names the ticket or alarm. + +## Retry semantics inverted + +Logging every retry attempt at WARN while the exhaustion, the moment the work actually moves to a backlog, is silent. The attempt is DEBUG, the exhaustion is ERROR with the identifier. + +## Non-context logging + +`logging.LogInfo` and friends without `Ctx` drop `request_id` and every business identifier from the MDC, which makes the line impossible to attach to anything. + +If the enclosing function has no `ctx` and it is a pure helper, do not thread `ctx` through several signatures only to log. Either move the line to the caller, which has the context, or drop it: a DEBUG line that cannot be correlated is close to useless when two work items are in flight. diff --git a/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/references/audit.md b/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/references/audit.md new file mode 100644 index 0000000..b4962e0 --- /dev/null +++ b/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/references/audit.md @@ -0,0 +1,77 @@ +# Auditing a service and the volume gate + +## Static audit + +Run from the checkout root of any Go service under `sources/project/`. Heuristic, not a linter: it flags short projection methods such as `toShortString()` as dumps, and it does not know about on-demand troubleshooting guards. Read what it prints; do not treat the counts as a gate on their own. + +```python +import re, glob + +files = [f for f in glob.glob('**/*.go', recursive=True) + if not f.endswith('_test.go') and '/vendor/' not in f] +info = dump = loop = noctx = 0 +for path in files: + depth, loops = 0, [] + for i, line in enumerate(open(path, errors='ignore'), 1): + stripped = line.strip() + if re.search(r'\bfor .*\{\s*$', stripped): + loops.append(depth) + depth += line.count('{') - line.count('}') + loops = [d for d in loops if d < depth] + if re.search(r'logging\.Log(Info|Debug|Warning|Error|Fatal)\(', line): + noctx += 1 + print(f'noCtx {path}:{i}: {stripped[:100]}') + if re.search(r'logging\.LogInfo(Ctx)?\(', line): + info += 1 + if '%+v' in line and not re.search(r'%\+v[^"]*"\s*,\s*len\(', line): + dump += 1 + print(f'dump {path}:{i}: {stripped[:100]}') + if loops: + loop += 1 + print(f'loop {path}:{i}: {stripped[:100]}') +print(f'INFO={info} dump={dump} loop={loop} noCtx={noctx}') +``` + +To exclude lines already behind an on-demand troubleshooting guard, track the brace depth of the block opened by `IsAlarmTroubleshootingActive(` and skip lines while inside it. In `gfiber-policy-executor` that moved the count from 77 INFO sites to 34 ungated ones, which is the number that matters. + +### How to read the output + +| Signal | Meaning | +|--------|---------| +| high `dump` against low `INFO` | the few INFO lines the service has are the expensive kind | +| any `loop` | a line scaling with item count rather than request count; the per-item result line is the one legitimate case | +| `noCtx` | lines that cannot be attached to a work item | + +## Volume gate + +Any change to logging on a high-volume path states its volume impact in the merge request. Measure the same scenario before and after, in the same namespace and window, using the `graylog-search` entry in [scripts/data/index.yaml](../../../scripts/data/index.yaml) with `--scope containers` and a container plus level filter, per [scripts/data/graylog-search.example.md](../../../scripts/data/graylog-search.example.md). + +Repeat for INFO, DEBUG, WARN and ERROR, then rerun on the branch build. + +| Metric | Before | After | Delta | +|--------|--------|-------|-------| +| INFO messages per run | | | | +| INFO bytes per run | | | | +| DEBUG messages per run | | | | +| WARN and ERROR per run | | | | +| Longest single INFO line, bytes | | | | + +Acceptance: INFO message count and INFO bytes must not increase. DEBUG is allowed to grow, since it is off in production. + +For SA services use the registered SVT cases from [skills/gfiber-svt-analysis/cases/index.yaml](../../gfiber-svt-analysis/cases/index.yaml). Services without an SVT case need a reference scenario agreed with the reviewer before the gate means anything. + +On the same run, confirm that a sample identifier from it is still findable at `LOG_LEVEL: INFO` with the SA alarm template from [queries/graylog/index.yaml](../../../queries/graylog/index.yaml). That is the regression the policy exists to prevent, and it is satisfied by the per-item result line rather than by anything new. + +## Merge request checklist + +The hard rules in [levels.md](levels.md) double as the review checklist. In addition: + +- Every new INFO line matches one of the four INFO cases. +- No new INFO line prints a collection, a struct or a body. +- No new INFO line sits inside a loop over domain objects. +- Every identifier list is capped. +- Every call is the `Ctx` variant. +- WARN and ERROR on failure paths carry the identifiers of the work they lost. +- The summary line is written from a `defer` that survives a panic. +- New metric labels come from a fixed vocabulary, with no identifiers in them. +- `go vet` is clean and no line prints a pointer address or a `%!s` marker. diff --git a/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/references/cases.md b/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/references/cases.md new file mode 100644 index 0000000..4c96764 --- /dev/null +++ b/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/references/cases.md @@ -0,0 +1,73 @@ +# Case catalogue + +The cases that come up in GFiber services and the level each one takes. If a case is not here, run the decision list in [levels.md](levels.md) and add a row. + +## Work intake and results + +| Case | Level | Note | +|------|-------|------| +| Request, batch or message arrived | INFO | counts and the values that identify the scope, such as alarm names, severities, OLT, HUT; no payload and no id list | +| Batch finished | INFO if ok, ERROR otherwise | one summary line with in, out, duration and status, written from a defer registered before any recover so a panic still produces it | +| Result of one work item | INFO | one per item, with its identifier and outcome; this is the line support greps for, and the one line that must never be demoted | +| Payload of the work item | DEBUG | or behind on-demand troubleshooting | +| Decision that ends the item | INFO | only when it is not already visible in that item's result message | +| Intermediate lookup or filter result | DEBUG | log the count at INFO if it matters, the members at DEBUG | +| Anything inside a loop over domain objects | DEBUG | plus one count after the loop | + +## Rejections and failures + +| Case | Level | Note | +|------|-------|------| +| Input malformed, null or failed validation | WARN | carry the identifiers that survived parsing, and the body size | +| Rejected for capacity or backpressure | WARN | one line per rejected request, never per item | +| No handler or policy matched the work | WARN | carry the identifiers, because no result line will be written | +| Upstream call failed, will be retried | DEBUG | the attempt is not yet a failure | +| Upstream call failed after retries | ERROR | carry the identifiers and the step that stopped | +| Some items succeeded, some failed | ERROR | on the summary line, with the split | +| Panic recovered | ERROR | log the recovered value and the stack, and keep serving | + +## Service lifecycle + +| Case | Level | Note | +|------|-------|------| +| Started, listeners bound, dependencies resolved | INFO | a handful of lines, once per process | +| Effective configuration | DEBUG | never secrets, tokens or credentials | +| Graceful shutdown | INFO | | +| Cannot start at all | FATAL | the only place FATAL is allowed | +| Database connection established | INFO | once at startup; per query is DEBUG | + +## Background work + +| Case | Level | Note | +|------|-------|------| +| Scheduled tick that found nothing to do | DEBUG | a tick every few seconds at INFO is one of the cheapest ways to burn retention | +| Scheduled tick that did work | INFO | one line with counts, not one per item | +| Kafka batch consumed | INFO | one summary per batch, same shape as an HTTP batch | +| One Kafka message processed | DEBUG | the per-item result line already covers what support needs | +| Message that cannot be parsed | ERROR | carry the message key and raise a metric; it will never parse, so it is lost work | +| Consumer rebalance or lag | none | leave it to the client library and to metrics | + +## Keep out + +| Case | Level | Note | +|------|-------|------| +| Health, liveness and readiness probes | none on success | probe traffic is constant; log only a failing probe | +| Every outbound HTTP request and response | DEBUG | rates and durations belong in metrics | +| Upstream returned an empty result | DEBUG | unless it changes the outcome, and then it belongs in the item's result message | +| Third-party library output | set it explicitly | do not let a dependency inherit DEBUG in production | +| Secrets, tokens, passwords | never | at any level | +| ONT serial, account id, hostname | not at INFO | on high-volume paths; fine in a bounded projection or at DEBUG | + +If a line has to be INFO and is still too frequent, sample it: log one in N with the count of what was skipped. Demoting it to DEBUG removes it from production entirely, which is usually not the intent. + +## Reference implementations + +Read these before writing a new one; both were reviewed against this policy. + +| What | Where | +|------|-------| +| Per-batch summary line, `key=value`, INFO on ok and ERROR otherwise | `gfiber-policy-executor`, `pkg/faultstatus/stats.go` | +| Per-alarm result line, the one support greps for | `gfiber-policy-executor`, `pkg/policies/executor.go` | +| Ingress line with counts, ids on a DEBUG companion | `gfiber-policy-executor`, `pkg/policies/executor.go` | +| Per-item result line from a defer, covering every failure path | `gfiber-ticketing-proxy`, `pkg/ticket/executor.go` | +| Rejection lines with a fixed reason vocabulary plus a counter | `gfiber-ticketing-proxy`, `pkg/ticket/routes.go` | diff --git a/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/references/levels.md b/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/references/levels.md new file mode 100644 index 0000000..0c13f5c --- /dev/null +++ b/submitted-skills/Gustavo Ruiz/skills/gfiber-logging/references/levels.md @@ -0,0 +1,112 @@ +# Levels and the decision list + +Canonical source: [How To: What logs belong at INFO, DEBUG, WARN and ERROR in GFiber services](https://bass.netcracker.com/display/GF/How+To%3A++What+logs+belongs+at+INFO%2C+DEBUG%2C+WARN+and+ERROR+in+GFiber+services). This file is the working copy for agents; when the two disagree, the BASS page wins. + +## Why there is a ceiling on INFO + +Graylog storage is shared across the platform. Every INFO line written on a healthy run is paid for in retention days, so the more a service logs, the shorter the window for grepping an incident that already happened. A service that logs too little is untriageable. The policy is the line between the two. + +Three facts that drive every rule below: + +- `LOG_LEVEL` is `INFO` in every shipped Helm chart. Treat DEBUG as not present in production. +- Support starts from one identifier, usually an alarm id or a ticket id, and searches Graylog full text. A decision that never printed that identifier cannot be found. +- Batch sizes are not capped upstream. A line inside a loop scales with item count, not with request count. + +## Levels + +| Level | Use for | Volume on a healthy run | +|-------|---------|-------------------------| +| ERROR | Work was lost and a human must look. Carries the identifiers of the lost work. | rare, each one actionable | +| WARN | An item was dropped or degraded and the service continues. Carries identifiers when no result line will be written. | rare | +| INFO | Work received, work finished, one result per work item. | O(1) per request or batch, plus one line per item | +| DEBUG | Everything else: intermediate collections, per-object detail, payloads, filter internals. | unbounded | +| FATAL | Cannot start and serve. Terminates the process. | startup only | + +`mano.netcracker.com/go-logging/v3` exposes `LogDebug`, `LogInfo`, `LogWarning`, `LogError`, `LogFatal` and a `Ctx` variant of each. There is no TRACE. + +## Decision list + +Walk in order, stop at the first yes. + +1. Work was lost and someone has to look at it. Use ERROR. +2. An item was dropped or degraded, and the service keeps going. Use WARN. +3. It is one of these four: work received, work finished, the result of one item, or a decision that ends an item and is not already in that item's result message. Use INFO. +4. It fires more than once per item, or prints a collection, a struct or a body. Use DEBUG. +5. Anything else. Use DEBUG. + +When two levels look defensible, take the lower one. A line at DEBUG can be recovered with on-demand troubleshooting or promoted next release. Retention days spent on a line nobody reads cannot. + +## WARN or ERROR + +The boundary that gets argued about most. + +- ERROR means the service could not do what it was asked and no automatic mechanism will fix it. A human has to look. +- WARN means the service did not do something, but that outcome is defined and expected in operation: input was unusable, capacity was full, a business rule dropped the item. + +The test: if this fires two hundred times tonight, does someone need to be paged? Yes is ERROR. No is WARN. + +Two consequences, both commonly got wrong: + +- A call that failed but will be retried automatically is not an ERROR on the attempt. The attempt is DEBUG. It becomes ERROR when the retries are exhausted and the work is actually lost. +- A validation rejection is never an ERROR, however loud it looks. The client sent something unusable and the service behaved correctly. That is WARN. + +## FATAL + +Startup only, and only when the process cannot serve at all: unreadable configuration, no database, a required dependency that will never appear. `LogFatal` terminates the process, so calling it on a request path turns one bad request into an outage. There is no case for FATAL after the service reports ready. + +## Field format + +`key=value` pairs, snake_case keys, prefixed by the subject of the line. Quote with `%q` only when the value can be empty or contain spaces. + +```go +logging.LogInfoCtx(ctx, "policy batch received: batch_id=%s policy=%q alarms=%d alarm_names=%s", + batchID, request.Policy, len(request.Alarms), distinctAlarmNames(request.Alarms)) +``` + +The runtime already adds a prefix, so do not repeat any of it in the message: + +``` +[2026-09-02T11:52:06.222] [INFO] [request_id=-] [tenant_id=-] [thread=-] [class=policies:executor.go:68] +``` + +### Correlation keys + +| Key | Source | Present on | +|-----|--------|-----------| +| `request_id` | MDC, from the cloud-core context propagation middleware | every line, automatically | +| `batch_id` | minted once at ingress, carried in the context | every line handling that batch | +| `alarm_id`, `ticket_id`, `order_id` | the domain object | every line naming a single work item | +| `alarm_ids` | capped list | lines describing a set | + +This is not structured logging. The logger emits a text message behind a fixed prefix, so Graylog does not extract these keys into searchable fields. They are found by full text search, which is exactly why identifiers have to appear literally in the message. + +## On-demand extended logging + +How a service gets full detail in production without raising `LOG_LEVEL` and without paying for it on every healthy run. Every service handling a high-volume work item should implement it. `gfiber-policy-executor` is the reference: + +``` +PUT /troubleshooting/{entityKey}?minutes=1440 +DELETE /troubleshooting/{entityKey} +GET /troubleshooting/{entityKey} +``` + +In code it is a guard around the verbose block, so the cost when off is one cached lookup: + +```go +logging.LogInfoCtx(ctx, "Handling Full Pon Loss for alarm: %+v", alarm.toShortString()) +if m.IsAlarmTroubleshootingActive(ctx, alarm) { + logging.LogInfoCtx(ctx, "Alarm (full): %+v", alarm.toFullString()) +} +``` + +The default line carries a bounded projection; the full payload is behind the guard. Setup and supported entity keys: [How to enable troubleshooting logs (gfiber-policy-executor)](https://bass.netcracker.com/pages/viewpage.action?pageId=2466165241). + +## Logs are not the only channel + +Choosing the right channel is most of the volume problem. + +| Channel | Answers | Cannot | +|---------|---------|--------| +| Service log (Graylog) | what happened to this specific id | show trends, and it costs shared retention | +| Prometheus metric | how often, how slow, alerting | carry an identifier; label cardinality forbids it | +| BLM `policy_actions_log` | what we did to this item, on the record | be found from the SA Graylog streams | diff --git a/submitted-skills/Marcos Silva/README.md b/submitted-skills/Marcos Silva/README.md new file mode 100644 index 0000000..ad4e70f --- /dev/null +++ b/submitted-skills/Marcos Silva/README.md @@ -0,0 +1,52 @@ +--- +name: marcos-silva-skills +description: Index for Marcos Silva's submitted Confluence + documentation skill set. +type: index +--- + +# Marcos Silva — Submitted Skills + +Tooling for creating, reviewing, and publishing Confluence pages in the Netcracker +BASS / AVP spaces, focused on `mcp-atlassian`, PlantUML diagrams, and pre-post +review. + +## Skills + +| Skill | Job | When to invoke | +|-------|-----|----------------| +| [confluence-page](skills/confluence-page/SKILL.md) | Create or update a Confluence page from a local storage-format draft via mcp-atlassian | Drafting a page, scaffolding from a template, mirroring content into a space | +| [page-reviewer](skills/page-reviewer/SKILL.md) | Audit a Confluence-ready body before it is posted | Just before `confluence_create_page_from_file` or `confluence_update_page_from_file` | +| [unslop](skills/unslop/SKILL.md) | Strip AI slop from prose before posting | After drafting, before review | +| [diagram-plantuml](skills/diagram-plantuml/SKILL.md) | Embed PlantUML correctly inside a Confluence page | Page needs a sequence, component, class, state, or activity diagram | + +## Scripts + +| Script | Purpose | +|--------|---------| +| [scripts/check-mcp-atlassian.sh](scripts/check-mcp-atlassian.sh) | Detect whether `mcp-atlassian` is wired up; print install hint if not | +| [scripts/new-page.sh](scripts/new-page.sh) | Scaffold a new page from a template into a draft folder | +| [scripts/dry-run-publish.sh](scripts/dry-run-publish.sh) | Pre-flight the page body (lint, slop-check, lint diagrams) without posting | + +## Templates + +See [templates/](templates/) for ready-to-fill body templates: + +- `hub-page.md` — overview / landing pages +- `how-to.md` — step-by-step runbook +- `rfc.md` — request for comment +- `postmortem.md` — incident write-up + +## Conventions + +Mirrors in `~/Netcracker/Projects/NDO/knowledge/confluence//` are +read-only local copies. Edit upstream, then re-pull — never patch the mirror +body in place. Skill bodies in this folder are the working copy for agents; +when a skill and the upstream page disagree, the upstream page wins and the +skill gets updated. + +## References + +- BASS Confluence — https://bass.netcracker.com +- mcp-atlassian upstream — https://github.com/sooperset/mcp-atlassian +- NDO knowledge base — `~/Netcracker/Projects/NDO/knowledge/` +- Cursor MCP approval status (governance) — see `BASS/cursor-mcps-approval-status.md` diff --git a/submitted-skills/Marcos Silva/scripts/check-mcp-atlassian.sh b/submitted-skills/Marcos Silva/scripts/check-mcp-atlassian.sh new file mode 100755 index 0000000..132a31b --- /dev/null +++ b/submitted-skills/Marcos Silva/scripts/check-mcp-atlassian.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# check-mcp-atlassian.sh +# Detect whether mcp-atlassian is wired into the active Claude / Cursor client. +# Prints PASS / MISSING with the install path that fits the current client. +# +# Usage: bash scripts/check-mcp-atlassian.sh +# Exit: 0 if installed, 1 if missing, 2 if check was inconclusive. + +set -u + +FOUND=0 +DETAILS="" + +# 1. The MCP server name shows up in the running client's config. +CANDIDATE_CONFIGS=( + "$HOME/.claude/settings.json" + "$HOME/.cursor/mcp.json" + "$HOME/.codex/config.yaml" + "$HOME/.claude.json" + "$(pwd)/.mcp.json" +) + +for cfg in "${CANDIDATE_CONFIGS[@]}"; do + if [[ -f "$cfg" ]]; then + if grep -qiE "mcp-atlassian|sooperset/mcp-atlassian" "$cfg" 2>/dev/null; then + FOUND=1 + DETAILS="$cfg" + break + fi + fi +done + +# 2. Active client processes. If the MCP is loaded we usually see a node / uv +# process with the server's name in argv. +if [[ $FOUND -eq 0 ]]; then + if command -v ps >/dev/null 2>&1; then + if ps -ef 2>/dev/null | grep -qiE "mcp-atlassian|sooperset.*atlassian"; then + FOUND=1 + DETAILS="(running process)" + fi + fi +fi + +# 3. npx cache. If installed globally, it lands here. +if [[ $FOUND -eq 0 ]]; then + if [[ -d "$HOME/.npm/_npx" ]] && find "$HOME/.npm/_npx" -type d -name "*atlassian*" 2>/dev/null | grep -q .; then + FOUND=1 + DETAILS="(npx cache)" + fi +fi + +if [[ $FOUND -eq 1 ]]; then + echo "PASS: mcp-atlassian detected in ${DETAILS:-unknown location}" + echo + echo "Verify the active client can see it:" + echo " - Claude Code : restart the session, then list /mcp" + echo " - Cursor : Cursor > Settings > MCP, look for 'mcp-atlassian'" + echo " - Codex CLI : /mcp list" + exit 0 +fi + +cat <<'EOF' +MISSING: mcp-atlassian is not wired into the active Claude / Cursor client. + +The Confluence + Jira tools you need are exposed by this MCP server: + https://github.com/sooperset/mcp-atlassian + +Install path depends on the client in use: + + Claude Code + claude mcp add atlassian \ + -e CONFLUENCE_URL=https://bass.netcracker.com \ + -e CONFLUENCE_USERNAME= \ + -e CONFLUENCE_API_TOKEN= \ + -- npx -y mcp-atlassian + # Add JIRA_* envs for Jira access too. + + Cursor (project-level .mcp.json) + { + "mcpServers": { + "atlassian": { + "command": "npx", + "args": ["-y", "mcp-atlassian"], + "env": { + "CONFLUENCE_URL": "https://bass.netcracker.com", + "CONFLUENCE_USERNAME": "", + "CONFLUENCE_API_TOKEN": "" + } + } + } + } + + Codex CLI + Add to ~/.codex/config.yaml: + mcp_servers: + atlassian: + command: npx + args: ["-y", "mcp-atlassian"] + env: + CONFLUENCE_URL: https://bass.netcracker.com + CONFLUENCE_USERNAME: + CONFLUENCE_API_TOKEN: + +Approval note: the BASS "Cursor MCPs approval status" page lists mcp-atlassian +as "Not approved" by default. Check the current row before relying on it for +governed spaces; if governance has not approved it yet, your post will land +but the space admin may revert the page. + +After install: restart the client, then re-run this script. +EOF +exit 1 diff --git a/submitted-skills/Marcos Silva/scripts/dry-run-publish.sh b/submitted-skills/Marcos Silva/scripts/dry-run-publish.sh new file mode 100755 index 0000000..8cf2830 --- /dev/null +++ b/submitted-skills/Marcos Silva/scripts/dry-run-publish.sh @@ -0,0 +1,151 @@ +#!/usr/bin/env bash +# dry-run-publish.sh +# Pre-flight a Confluence storage body before posting. Runs: +# - format sanity (storage XHTML, no wiki markup, no markdown fences) +# - secret / PII grep (BLOCKER) +# - macro sanity (every {code} / {plantuml} / panel is in storage form) +# - PlantUML parse (if plantuml on $PATH) +# - size sanity (over 300 lines needs justification header) +# +# Usage: +# bash scripts/dry-run-publish.sh +# +# Exit codes: +# 0 = ready to post +# 1 = REVISE (MAJOR or MINOR issues found) +# 2 = BLOCK (BLOCKER issues found) + +set -u + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +DRAFT="$1" + +if [[ ! -f "$DRAFT" ]]; then + echo "Draft not found: $DRAFT" >&2 + exit 2 +fi + +BLOCK=0 +MAJOR=0 +MINOR=0 + +note_block() { echo " [BLOCK] $1"; BLOCK=1; } +note_major() { echo " [MAJOR] $1"; MAJOR=1; } +note_minor() { echo " [MINOR] $1"; MINOR=1; } + +echo "Pre-flight: $DRAFT" +echo "------------------------------------" + +# 1. Format sanity +if head -3 "$DRAFT" | grep -q '^---$'; then + note_block "Markdown front-matter detected -- storage body must not contain --- fences." +fi + +if grep -qE '\{code:' "$DRAFT"; then + note_major "Wiki code-block syntax detected. Use ." +fi +if grep -qE '\{info:' "$DRAFT" || grep -qE '\{note:' "$DRAFT" || grep -qE '\{warning:' "$DRAFT"; then + note_major "Wiki panel syntax detected. Use ." +fi +if grep -qE '\{plantuml' "$DRAFT"; then + if ! grep -qE '." + fi +fi + +if grep -qE '^#{1,6} ' "$DRAFT"; then + note_major "Markdown heading detected (# / ## / ###). Use

/

/

." +fi +if grep -qE '^[[:space:]]*```' "$DRAFT"; then + note_major "Markdown code fence (\`\`\`) detected. Use ." +fi + +# 2. Secrets / PII +SECRET_PATTERNS=( + 'AKIA[0-9A-Z]{16}' + 'ghp_[A-Za-z0-9]{30,}' + 'glpat-[A-Za-z0-9_-]{20,}' + 'xox[baprs]-[A-Za-z0-9-]{10,}' + 'sk-[A-Za-z0-9]{40,}' + 'ATATT[A-Za-z0-9]{30,}' + '-----BEGIN [A-Z ]+PRIVATE KEY-----' +) + +for pat in "${SECRET_PATTERNS[@]}"; do + if grep -qE "$pat" "$DRAFT" 2>/dev/null; then + note_block "Secret pattern matched: $pat -- scrub before posting." + fi +done + +if grep -qE "Netcracker/Projects/NDO/knowledge" "$DRAFT"; then + note_block "Body references the local mirror path. Use the public BASS URL." +fi + +# 3. Macro sanity +PLANTUML_COUNT=$(grep -cE '/dev/null || echo 0) +CODE_COUNT=$(grep -cE '/dev/null || echo 0) + +if [[ $PLANTUML_COUNT -gt 0 ]]; then + if grep -B2 'ac:name="plantuml"' "$DRAFT" | grep -qE '/dev/null 2>&1; then + TMPDIR_PRE=$(mktemp -d) + awk ' + //{flag=0} + flag && /<\/ac:plain-text-body>/{capture=0; next} + flag && capture{print} + ' "$DRAFT" > "$TMPDIR_PRE/all.puml" + if [[ -s "$TMPDIR_PRE/all.puml" ]]; then + if ! plantuml -tpng -checkonly -failfast2 "$TMPDIR_PRE/all.puml" >/dev/null 2>&1; then + note_major "PlantUML syntax check failed. Run plantuml -tpng locally on the extracted body." + fi + fi + rm -rf "$TMPDIR_PRE" +fi + +# 4. Size +LINES=$(wc -l < "$DRAFT") +if [[ $LINES -gt 300 ]]; then + if ! head -5 "$DRAFT" | grep -qiE 'justify|long|expanded'; then + note_major "Body is $LINES lines (>300) and no justification header is present." + fi +fi + +# 5. Image alt text +if grep -qE ' without ac:alt." + fi +fi + +echo "------------------------------------" +if [[ $BLOCK -eq 1 ]]; then + echo "BLOCK -- secret, format, or path issue. Fix and re-run." + exit 2 +elif [[ $MAJOR -eq 1 ]]; then + echo "REVISE -- major issues found. Fix and re-run." + exit 1 +elif [[ $MINOR -eq 1 ]]; then + echo "PASS (with minor notes) -- ready to post." + exit 0 +else + echo "PASS -- ready to post." + exit 0 +fi diff --git a/submitted-skills/Marcos Silva/scripts/new-page.sh b/submitted-skills/Marcos Silva/scripts/new-page.sh new file mode 100755 index 0000000..84e0fd2 --- /dev/null +++ b/submitted-skills/Marcos Silva/scripts/new-page.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# new-page.sh +# Scaffold a new Confluence page draft from a template into the local drafts +# folder. The draft is storage-format XHTML, ready to fill and post. +# +# Usage: +# bash scripts/new-page.sh [template] +# space : AVP, BASS, etc. (see confluence-page/references/space-keys.md) +# title : Page title; spaces become + in the storage path +# template : hub | how-to | rfc | postmortem (default: hub) +# +# Writes to: +# ~/Netcracker/Projects/NDO/knowledge/confluence/drafts/<SPACE>/<slug>.xml +# +# Exit: 0 on success, 1 on bad args, 2 on missing template. + +set -eu + +if [[ $# -lt 2 ]]; then + echo "Usage: $0 <space> <title> [template]" >&2 + exit 1 +fi + +SPACE="$(echo "$1" | tr '[:lower:]' '[:upper:]')" +TITLE="$2" +TEMPLATE="${3:-hub}" + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(dirname "$SCRIPT_DIR")" +TEMPLATE_FILE="$ROOT/templates/${TEMPLATE}.md" + +if [[ ! -f "$TEMPLATE_FILE" ]]; then + echo "Template not found: $TEMPLATE_FILE" >&2 + echo "Available templates:" >&2 + ls "$ROOT/templates" 2>/dev/null | sed 's/\.md$//' | sed 's/^/ - /' >&2 + exit 2 +fi + +DRAFT_ROOT="${DRAFT_ROOT:-$HOME/Netcracker/Projects/NDO/knowledge/confluence/drafts}" +DRAFT_DIR="$DRAFT_ROOT/$SPACE" +mkdir -p "$DRAFT_DIR" + +SLUG="$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | tr ' /' '--' | tr -cd 'a-z0-9-_')" +DRAFT_FILE="$DRAFT_DIR/${SLUG}.xml" + +{ + echo '<?xml version="1.0" encoding="UTF-8"?>' + echo "<page xmlns:ac=\"http://atlassian.com/content\" xmlns:ri=\"http://atlassian.com/resource/identifier\">" + echo " <title>$TITLE" + echo " $SPACE" + echo " " + echo "

$TITLE

" + echo "

Drafted $(date -u +%Y-%m-%d). Edit the body below this line; the title and space are set above.

" + echo "" + echo "" + echo "" + echo "

Body starts here.

" + echo "" + echo " " + echo "" +} > "$DRAFT_FILE" + +echo "Draft created: $DRAFT_FILE" +echo "Space: $SPACE" +echo "Title: $TITLE" +echo "Template: $TEMPLATE" +echo +echo "Next:" +echo " 1. Fill the body between and using storage XHTML." +echo " 2. Run bash $SCRIPT_DIR/dry-run-publish.sh \"$DRAFT_FILE\"" +echo " 3. Post via mcp-atlassian: confluence_create_page_from_file." diff --git a/submitted-skills/Marcos Silva/skills/confluence-page/SKILL.md b/submitted-skills/Marcos Silva/skills/confluence-page/SKILL.md new file mode 100644 index 0000000..13a0d65 --- /dev/null +++ b/submitted-skills/Marcos Silva/skills/confluence-page/SKILL.md @@ -0,0 +1,147 @@ +--- +name: confluence-page +description: Create or update a Confluence page on BASS from a local storage-format draft, using mcp-atlassian. Use when scaffolding a new page in AVP or BASS, mirroring a doc into a space, or updating an existing page by id or by space+title. +--- + +# Confluence Page + +Draft a page in storage format locally, lint it, then post or update it via +`mcp-atlassian`. The skill never edits a page in place without a draft file on +disk and a pre-flight pass. + +Canonical source: [BASS Confluence](https://bass.netcracker.com). When the +skill and a BASS page disagree, BASS wins and this skill gets updated. + +## Hard rules + +- **Storage format, not wiki markdown.** Confluence Cloud expects the + `body.storage` representation. Wiki markup only renders correctly when the + page's renderer is configured for it; do not assume. +- **No secrets, tokens, customer PII, or session cookies** in any body. + `references/secrets.md` lists the patterns to scrub. +- **Title is unique within the parent** — verify with `confluence_search` or + `confluence_get_page(spaceKey, title)` before creating. +- **PlantUML goes through the `{plantuml}` macro** at body root, never inside + an info panel or a code block — see `diagram-plantuml` skill. +- **Attachments go through the attachments API**, not as base64 in the body. + See `references/attachments.md`. +- **One page per draft file.** Don't stuff multiple pages into one storage file; + split before posting. + +## Workflow: new page + +1. Pick a template from `templates/` and copy it to a scratch file under + `~/Netcracker/Projects/NDO/knowledge/confluence/drafts//.xml` + (`` is the space key, e.g. `AVP`, `BASS`). +2. Decide the parent. Default parent is the space home for top-level pages. + Use `confluence_search` to find the parent id when nesting. +3. Fill the body. Storage format uses standard XHTML; the only macros that + survive the round trip are listed in `references/macros.md`. +4. Run `scripts/dry-run-publish.sh ` — it lints the body, runs the + `unslop` pass, and verifies every `{plantuml}` block parses. +5. `mcp__atlassian.confluence_create_page(spaceKey, title, storageFilePath, + parentId?)` to post. The MCP tool reads the file directly; never paste the + body into the call. +6. Capture the new page id in `~/Netcracker/Projects/NDO/knowledge/confluence/_index.md` + so it appears in the local mirror index. + +## Workflow: update existing page + +1. Resolve the page id. `confluence_get_page(spaceKey, title)` if you know the + title, otherwise `confluence_search(cql="title=\"…\"")`. +2. Fetch the current storage body with `confluence_get_page_content(pageId)` + and save it next to your draft under + `confluence/drafts//.from-server.xml`. This is your safety net. +3. Diff your draft against the server copy. If a section was renamed upstream + but is still wanted locally, carry the change forward; if it was deleted, + drop it. +4. Run `scripts/dry-run-publish.sh `. +5. `mcp__atlassian.confluence_update_page_from_file(pageId, storageFilePath, + title?, minorEdit=true, versionMessage="…")`. Default `minorEdit` to true; + only set false for content rewrites. +6. If the diff touched more than the section you set out to change, stop and + re-pull the page before posting. + +## Workflow: mirror a markdown file into Confluence + +1. Run the page-reviewer skill first. Mirrors must not introduce slop into a + governed space. +2. Convert headings from `#`/`## `###` to `h1`/`h2`/`h3`. Strip any leading + front-matter — the storage body must not contain `---` fences. +3. Strip any path that leaks the local mirror root + (`/home/masi1023/Netcracker/Projects/NDO/knowledge/...`). Use the public + BASS URL instead. +4. Convert `[[wikilinks]]` to plain text or proper Confluence links; the wiki + linker only resolves inside BASS. +5. Convert fenced code blocks to ``. +6. Run the dry-run script. + +## Body format cheatsheet + +The MCP server expects a UTF-8 file containing a fragment of storage XHTML. +Common elements: + +| You want | Storage format | +|----------|----------------| +| Heading | `

` | +| Paragraph | `

` | +| Bold / italic | `` / `` | +| List | `
` / `
` | +| Table | `
` | +| Info panel | `` | +| Code block | `` | +| PlantUML | `` | +| Link | `label` | +| Page link | `` | + +Full macro catalog: [references/macros.md](references/macros.md). + +## Picking the parent page + +- Top-level page under the space home: omit `parentId` (MCP defaults to the + space home) or pass the space home id explicitly. +- Nested under a hub or domain page: find the parent id with + `confluence_search(cql="space=AVP AND title~\"Hub\"")` and pick by hand. +- Moving a page later is a separate API call; do not "fix" the parent by + deleting and recreating — that loses history, watchers, and reactions. + +## Picking the space + +| Content kind | Space | +|--------------|-------| +| NDO product docs | `AVP` | +| Internal team / governance / how-to | `BASS` | +| Customer-facing release notes | check with the page owner | +| Personal scratch | do **not** post to BASS / AVP; keep in `~/Netcracker/Projects/NDO/knowledge/` | + +If unsure, ask before posting. + +## MCP availability + +`mcp-atlassian` is listed in the +[BASS Cursor MCPs approval page](https://bass.netcracker.com/display/~seby0316/Cursor+-+MCPs+approval+status) +as *Not approved* by default — that page was last synced 2026-06-11; check the +current status before relying on it. The skill assumes the MCP server is wired +into the active Claude / Cursor client. Run `scripts/check-mcp-atlassian.sh` +to detect it and get an install hint if missing. + +## Safety + +- **Read-only on `~/Netcracker/Projects/NDO/knowledge/confluence//`.** + Mirrors are snapshots. Never edit them in place — re-pull instead. +- **Drafts live under `confluence/drafts/`** and are the only files this + skill writes to by default. +- **No page deletion** through this skill. Deletes are not undoable and lose + history. If a page must go, ask in the page's comments first. +- **Never paste body content into the API call** — pass a file path so the + body stays reviewable in git. + +## Related + +| Skill | Role | +|-------|------| +| `page-reviewer` | Mandatory pre-post gate; runs before any create/update | +| `unslop` | Removes AI phrasing so the page reads as Netcracker voice | +| `diagram-plantuml` | Owns the `{plantuml}` macro and the diagram macro catalog | +| `confluence-to-slides` (existing) | Pulls a finished page into a slide deck | diff --git a/submitted-skills/Marcos Silva/skills/confluence-page/references/attachments.md b/submitted-skills/Marcos Silva/skills/confluence-page/references/attachments.md new file mode 100644 index 0000000..280a2e1 --- /dev/null +++ b/submitted-skills/Marcos Silva/skills/confluence-page/references/attachments.md @@ -0,0 +1,68 @@ +# Attachments + +Attachments live on a page and are referenced by filename. They survive page +moves and template changes, but they do not survive page deletion. + +## Upload via mcp-atlassian + +```python +mcp__atlassian.confluence_upload_attachment( + pageId=…, + filePath="path/to/file.png", + comment="optional version note", +) +``` + +Returns a metadata object including the download URL. Use that URL inside the +page body, not a local file path. + +## Reference in the body + +By attachment filename: + +```xml + + + + +``` + +As an inline image: + +```xml + + + +``` + +Always set `ac:alt` for accessibility: + +```xml + + + Sequence diagram of the order → inventory → shipment flow. + +``` + +## What NOT to do + +- Don't paste base64 PNG into the body. The page editor can't replace it + without re-rendering the whole page; it bloats the storage body; the page + cannot be reviewed by lint. +- Don't link to a public CDN. BASS pages are private; CDN URLs leak and break + on access-controlled spaces. +- Don't re-upload the same file under a new name. Confluence deduplicates by + hash within a page, but the editor doesn't surface duplicates well. + +## Versioning + +Attach with a version suffix (`diagram-v2.png`) when updating. Confluence +keeps the old version in the attachments list and the page body continues to +reference the filename; change the filename in the body to point at the new +version. + +## Cleanup + +Pages with stale attachments show up in the space's attachment report. When +removing a diagram, also remove the attachment (do not leave orphaned files +on the page). diff --git a/submitted-skills/Marcos Silva/skills/confluence-page/references/macros.md b/submitted-skills/Marcos Silva/skills/confluence-page/references/macros.md new file mode 100644 index 0000000..e31c32d --- /dev/null +++ b/submitted-skills/Marcos Silva/skills/confluence-page/references/macros.md @@ -0,0 +1,112 @@ +# Confluence Storage Macros + +Confluence Cloud storage format accepts a fixed set of macros. Anything not in +this catalog either renders as plain text or fails silently. Before adding a +new macro to a draft, check the name here. + +## Inline + +| Macro | When | +|-------|------| +| `{code}` | Fenced code with optional language | +| `{plantuml}` | Diagrams — see `diagram-plantuml` skill | +| `{info}` | Info panel | +| `{note}` | Note panel | +| `{warning}` | Warning panel | +| `{tip}` | Tip panel | +| `{excerpt}` | Reusable fragment; also `excerpt-include` | +| `{anchor}` | Inline anchor for `{pageref}` | +| `{pageref}` | Cross-page reference by anchor | +| `{children}` | Lists child pages | +| `{include}` | Includes another page (full or excerpt) | +| `{table-of-content}` | Outline from heading hierarchy | +| `{expand}` | Collapsible section | +| `{status}` | Coloured status pill | +| `{cheese}` | Image gallery — prefer `image` element instead | +| `{noformat}` | Plain monospace, no language hint | + +## Panels + +Panels take rich-text bodies. PlantUML inside a panel does not render — put +diagrams at body root. + +```xml + + +

Body goes here.

+
+
+``` + +Available panel macros: `info`, `note`, `warning`, `tip`, `success`, +`error`, `panel` (generic). + +## Code block + +```xml + + python + example.py + true + + +``` + +`language` accepts the short names from Confluence's language list (`python`, +`java`, `javascript`, `typescript`, `go`, `bash`, `sql`, `json`, `yaml`, +`xml`, `markdown`). Anything outside the list falls back to plain monospace. + +## Tables + +Standard XHTML tables. Confluence does not need the `` wrapper for plain tables. + +```xml + + + + + + + + + + + +
Column AColumn B
cellcell
+``` + +For sortable or filterable tables, use the `table-plus` macro — but only +when the table is genuinely worth the overhead. + +## Links + +- External: `label` +- Page by title: `` +- Page by id: `` +- Attachment: `` +- User mention: `` + +## Attachments + +Attachments go through `mcp__atlassian.confluence_upload_attachment` / +`confluence_create_page_from_file` (with the file path) — never as base64 in +the body. See `attachments.md`. + +## What is NOT a macro + +| Construct | Status | +|-----------|--------| +| Wiki markup (`{code}…{code}`) | Renders only on pages whose renderer is set to wiki; do not assume | +| Markdown fences | Not interpreted; render as text | +| HTML5 `
` | Rendered as plain HTML; works but no styling | +| Inline SVG | Works but is not editable through the page editor; prefer PlantUML | +| `