publish-npm
Skill0 starsChanged 4 months agoMITView on GitHub
Publica o pacote `@jnerytech/specd` no registry npm usando o token do .env, com gate verde, dry-run revisado, commit e push antes da publicação, confirmação explícita do autor e releitura do registry como prova. Use quando o usuário pedir para publicar no npm, subir versão, lançar release, rodar npm publish, depreciar uma versão publicada, ou perguntar por que uma versão no registry não roda — e também quando pedir só o dry-run ou verificar o que iria no tarball.
- Reads credentials
- Commits and pushes
1 other project uses a version of this.
Adopted by
Most recently taken up by
- phucbm/skillstheir own version, first seen Oct 13, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- phucbm/skills★ 2same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository13 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- claude-code-memorySkill
- docgenSkill
- handoffSkill
- openspec-apply-changeSkill
- openspec-archive-changeSkill
- openspec-exploreSkill
- openspec-proposeSkill
- specd-apply-changeSkill
- specd-archive-changeSkill
- specd-exploreSkill
- specd-proposeSkill
The file
Files
publish-npm
Publica specd no registry npm. O procedimento existe porque publicação é a
operação mais irreversível que este repositório executa: nome mais versão
publicados nunca voltam a ficar livres. npm unpublish só vale nas primeiras
72 horas, e mesmo assim queima o par para sempre.
Daí a skill seguir costly-ops-are-not-silent e absence-is-not-compliance do
CLAUDE.md ao pé da letra: para e nomeia a escolha antes de pagar o custo, e
trata a resposta de sucesso do npm como promessa, não como prova.
Regras que não se negociam
A credencial nunca aparece. O token mora em NODE_AUTH_TOKEN no .env, que
é gitignored. Nunca cat .env, nunca echo do valor, nunca token em argv
(fica visível em ps para qualquer processo da máquina). Ele entra por um
.npmrc temporário fora do repositório, apagado por trap mesmo se o comando
falhar.
As outras quatro são pré-condições, cada uma detalhada no passo que a executa:
- Sem confirmação nesta conversa, não publica — versão e dist-tag exatas, depois do dry-run (passo 5). Autorização de sessão anterior não vale.
- Sem gate verde e árvore limpa, não publica (passos 2 e 6).
- De commit que só existe nesta máquina, não publica (passo 6).
- Sucesso do npm não é prova — quem fecha é a releitura (passo 8).
Procedimento
1. Estado antes de qualquer coisa
git status --porcelain && git rev-parse --short HEAD
node -p "const p=require('./package.json'); p.name+'@'+p.version"
npm view "$(node -p "require('./package.json').name")" versions --json 2>&1
npm view "$(node -p "const p=require('./package.json'); p.name+'@'+p.version")" version 2>&1Árvore suja → pare e mostre o que está pendente. A lista de versões sai inteira,
sem tail: o começo da série é onde mora 0.0.0, a versão quebrada que os
passos 4 e 8 usam como referência.
Terceira linha, o nome. E404 significa que este nome nunca foi
publicado — normal numa primeira publicação, alarme em qualquer outra. Nome
diferente é pacote diferente, não versão nova do mesmo: houve um intervalo em que
@jnerytech/specd estava no ar e o package.json dizia "name": "specd", e
publicar dali teria reservado um segundo nome, com quem instalou o primeiro
continuando nele. Se divergirem, pare e traga a escolha ao autor — renomear
pacote publicado quebra quem instalou. (REQ-CLI-007 amarra o nome citado na
documentação ao manifesto; a checagem fica porque é barata e falha em silêncio.)
Quarta linha, o par exato. Versão de volta → já publicada, e não há
publicação sem bump; leve ao passo 3 agora, não depois. E404 → livre. Sem essa
pergunta o conflito só aparece no dry-run, e apareceu: a 0.1.1 começou com o
package.json marcando 0.1.0, já no ar desde a release anterior.
Pacote escopado é restricted por padrão. --access public no publish é
obrigatório, não decorativo — sem ele o pacote sobe privado e a instalação
pública falha com 404 autenticado.
2. Gate
npm run verifyPrecisa sair 0. verify roda format, que escreve arquivos — recheque git
status --porcelain depois. Se sujou, o commit é do autor, não da skill.
3. Versão
Decida com o usuário; não invente bump. npm view <pacote> versions --json diz o
que está ocupado — não deduza do package.json, que costuma marcar a última
publicada e não a próxima. Versão já publicada é rejeitada com
EPUBLISHCONFLICT: sinal de que faltou bump, nunca de que cabe --force.
npm version <patch|minor|major> cria commit e tag anotada. É escrita no
histórico — confirme antes. A tag nasce local; quem a leva ao remoto é o passo 6,
antes da publicação e não depois.
Mas o bump pode já ter acontecido, e sem tag. package.json marcando uma
versão livre não quer dizer que npm version rodou — alguém pode ter editado o
campo à mão dentro de um commit de feature. Foi o caso da 0.3.0, escrita em
38df4ef, com a série v0.0.1…v0.2.0 toda anotada e nenhuma v0.3.0:
git log -S'"version": "<versão>"' --oneline -- package.json
git tag -l "v<versão>"Primeiro vazio → o bump não foi commitado; use o npm version acima. Primeiro
responde e segundo vazio → a versão já está no histórico sem tag, e criá-la é
decisão do autor, não conserto silencioso: pergunte junto com a confirmação do
passo 5 e crie anotada no passo 6, apontando para o commit de onde o tarball
sai — o HEAD que o passo 4 construiu, não o que mexeu no package.json.
4. Dry-run e revisão do tarball
npm run build && ls dist/cli.js
node -p "JSON.stringify(require('./package.json').files)"
npm publish --dry-runO build explícito não é redundância do passo 2: dist/ é gitignored, e
padrão de files que não casa nada não é erro para o npm — ele publica o que
sobrou e sai 0.
Confira na saída, item por item:
dist/cli.jsna lista de arquivos. Este é o item que já falhou de verdade:@jnerytech/specd@0.0.0está publicado com três arquivos —package.json,README.mdeLICENSE— e nenhumdist/. Obinaponta paradist/cli.js, que não existe no tarball, então o pacote instala e o binário não roda. O publish saiu 0 e o registry aceitou.- Contagem de arquivos e
unpackedSizecompatíveis com um build de TypeScript: a0.3.0subiu com 95 arquivos e 416 kB desempacotados. Três arquivos e 8 kB é o tamanho de um pacote sem código; salto na direção oposta é vazamento de conteúdo. - A lista contra o
filesreal, que não é sódist/— hoje é["dist","skills"], porque as quatroskills/*/SKILL.mdviajam no tarball desde985f12be ospecd init --skillsas instala. Ler o campo antes de julgar a lista evita os dois erros simétricos: tratar entrada legítima como vazamento, e o contrário. Fora do quefilesdeclara, o npm inclui semprepackage.json,README.mdeLICENSE. Qualquer.env,.specd/,sandbox/,test/ounode_modules/é abortar e corrigirfiles.
Mostre esse resumo ao usuário — é o que ele vai aprovar — e guarde o shasum,
que o passo 8 confere.
Duas coisas na saída parecem defeito e não são:
EPUBLISHCONFLICTnão invalida a revisão. Versão já publicada faz o dry-run sair não-zero, e ainda assim ele imprime a lista de arquivos e osTarball Detailsinteiros. Só o número da versão muda depois do bump; não pule a revisão ao repetir o dry-run.default accessna linha final não contradiz--access public. O dry-run não recebe a flag, então relata o padrão do pacote. Quem responde sobre acesso é o publish do passo 7, que imprimepublic access.
5. Confirmação
Pergunte, nomeando os três: pacote, versão e dist-tag. Diga na mesma frase que o par nome+versão é permanente. Sem resposta afirmativa explícita, pare aqui — parar é um resultado legítimo desta skill.
6. Commit e push, antes de publicar
O registry é permanente e o working tree não. Publicar de um commit que só existe
nesta máquina cria uma versão imutável cujo fonte some com um rebase, um
--amend ou um disco morto. Na direção inversa vale igual: publicar antes de
empurrar deixa uma janela em que o registry está adiante do GitHub, e quem for
olhar o código da versão recém-anunciada não o encontra. É a escolha de direção
de REQ-ARC-012 — o lado recuperável adiante, nunca o irreversível.
git status --porcelain # tem que sair vazio
git log --oneline @{u}..HEAD
git tag -a v<versão> -m "<nota>" <commit do dry-run> # só se o passo 3 apurou tag faltando
git push --follow-tags origin main
git ls-remote --tags origin | grep "v<versão>"--follow-tags só empurra tag anotada, e não cria nenhuma. Tag leve ele
ignora em silêncio — o push sai 0, os commits sobem e a tag fica para trás; foi o
que houve na 0.2.0, cuja tag um git tag -f posterior recriou leve. E quando
não existe tag alguma (o bump à mão do passo 3), o bloco sobe zero tags e nada
avisa: o registry recebe uma versão que o repositório não marca. Por isso a linha
do ls-remote, e por isso ela não é git tag -l, que responderia sobre o
repositório local.
Duas linhas no ls-remote — refs/tags/v<versão> e refs/tags/v<versão>^{} —
é a assinatura de tag anotada, e a segunda tem que ser o commit do dry-run. Uma
linha só é leve. Para mover uma tag, recrie-a anotada: git tag -f -a v<versão>
-m "<nota>" <commit> e depois git push origin v<versão> --force.
Se algo mudou no repositório entre o passo 4 e aqui — README corrigido,
arquivo formatado — o dist/ e o dry-run envelheceram. Refaça os dois; o tarball
que o usuário aprovou tem que ser o tarball que sobe.
Push é escrita em sistema de terceiro, então vale costly-ops-are-not-silent: diga o que vai subir — quantos commits, qual tag — antes de empurrar.
7. Publicação
set -a; . ./.env; set +a
[ -n "$NODE_AUTH_TOKEN" ] || { echo "NODE_AUTH_TOKEN missing from .env"; exit 2; }
NPMRC="$(mktemp)"; trap 'rm -f "$NPMRC"' EXIT
printf '//registry.npmjs.org/:_authToken=%s\n' "$NODE_AUTH_TOKEN" > "$NPMRC"
npm --userconfig "$NPMRC" whoami
npm --userconfig "$NPMRC" publish --access publicwhoami primeiro: token inválido ou expirado falha ali, antes de qualquer
escrita. Se ele responder, a credencial serve.
Erros com tratamento próprio:
ENEEDAUTH/E401— token inválido, expirado ou sem escopo de publicação. Não tente outro caminho de autenticação; peça um token novo ao usuário.EOTP— a conta exige 2FA. O token sozinho não passa: peça o código e repita o mesmo comando com--otp <código>.E403— nome ocupado por outro autor, ou conta sem direito sobre o pacote. Pare: isso não se resolve com flag.
8. Prova (absence-is-not-compliance)
O +<pacote>@<versão> impresso pelo publish é a resposta de quem recebeu o
pedido. A releitura é a primeira prova:
npm view --prefer-online <pacote>@<versão> version dist-tags dist.fileCount dist.unpackedSize dist.shasumVersão de volta → o registro existe, e o dist.shasum tem que bater com o do
dry-run e o do publish. Três iguais → o tarball revisado é o que está no ar.
E404 aqui não significa "não publicado" — significa "não sei". npm view e
npx leem cache local e CDN, e nos primeiros segundos depois do publish ambos
respondem sobre um mundo anterior. Aconteceu com a 0.3.0: publish às 20:22:00,
provas às 20:22:05, E404 na primeira e ETARGET na segunda, com o pacote
perfeitamente no ar. --prefer-online estreita a janela mas não a fecha — quem
fecha é o documento cru, que não passa por cache do npm:
curl -s https://registry.npmjs.org/<pacote com / escapado como %2F> \
| node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const p=JSON.parse(s);console.log(Object.keys(p.versions).join(', '));console.log(JSON.stringify(p['dist-tags']));console.log(p.time['<versão>']);})"São três estados, não dois — absence-is-not-compliance aplicado à própria ferramenta de prova:
| Documento cru | npm view | Leitura |
| tem a versão | tem | publicado |
| tem a versão | E404 | propagação; repita com --prefer-online até responder |
| não tem | E404 | não publicado, mesmo com o passo 7 em 0 |
Só a terceira linha é falha. Relate-a e investigue; não republique por reflexo — e não conclua a terceira sem ter perguntado ao documento cru, porque a segunda se parece exatamente com ela.
E registro não é pacote utilizável. npm view responder prova que o nome
está ocupado, não que o que subiu funciona — foi assim que
@jnerytech/specd@0.0.0 ficou no ar sem dist/, respondendo 0.0.0 a quem
perguntasse. A segunda prova não é opcional:
cd "$(mktemp -d)" && npx -y --prefer-online <pacote>@<versão> --helpRodou e imprimiu o uso → publicado de verdade. could not determine executable
ou MODULE_NOT_FOUND → subiu pacote quebrado, e o conserto é uma versão nova,
porque a que está no ar não se corrige no lugar. ETARGET não fala sobre o
conteúdo: é a janela de propagação da tabela acima, e a resposta é repetir.
9. Depois
Nada disto é automático — são escritas no repositório, e a decisão é do autor:
- Commits e tag já subiram no passo 6. Se algo foi commitado depois da publicação, ele descreve uma versão que já está no ar: empurre também, senão o remoto conta uma história diferente da que o registry entregou.
- Há teste amarrando o nome e o caminho citados na documentação ao
namee aobindopackage.json— se README, CLAUDE.md ou AGENTS.md mudarem, rodenpm testantes de commitar. Versão que subiu quebrada não se corrige no lugar, mas se marca:
npm deprecate <pacote>@<versão> "<motivo, e para onde ir>". Diferente deunpublish, é reversível — string vazia desfaz — e não tem janela de 72h. Depreciar versão que funciona só porque é antiga avisa todo instalador à toa; o critério é defeito, não idade.Confira pelo documento cru, não pelo seletor de campo:
npm view <pacote>@<versão> deprecatedimprime vazio mesmo quando a versão está depreciada, e vazio se lê como "não pegou". Quem responde énpm view <pacote>@<versão> --jsonou ocurldo passo 8. É o mesmo modo de falha da tabela de lá: o comando que parecia conferir devolve silêncio, indistinguível do estado que deveria detectar.
Este arquivo envelhece como qualquer documentação, e aqui teste nenhum
alcança. Ao terminar uma publicação, releia os passos 1, 3, 4 e 8 — citam
estado do registry e do package.json, que mudam justamente quando esta skill
roda. A 0.3.0 cobrou o preço em três de uma vez: files já listava skills e
o passo 4 dizia só dist/; o bump viera à mão sem tag e o passo 3 só previa
npm version; o passo 8 lia E404 de propagação como não-publicado. Nenhum
apareceria em revisão de código — só rodando.
O que esta skill não faz
Não mexe em publishConfig, não usa --provenance (exige OIDC de CI, não roda
local) e não faz npm unpublish — despublicar é decisão do autor no terminal
dele, com as consequências à vista.
Não decide o nome do pacote. Escopado e não escopado são dois produtos diferentes para o registry, e escolher por conveniência é a decisão cara e silenciosa que costly-ops-are-not-silent proíbe.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

