@nynb/sandpaper 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.md +28 -4
- package/bin/verify-publish.js +46 -0
- package/package.json +30 -7
- package/skill/sandpaper/SKILL.md +2 -1
- package/skill/sandpaper/commands/help.md +1 -0
- package/skill/sandpaper/commands/release.md +45 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Sandpaper are recorded here. Format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
Each entry is drafted from `brain/log.html` — the same record the agent keeps of its
|
|
8
|
+
own work — via `/sandpaper:release`, never written from scratch.
|
|
9
|
+
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
## [0.2.0] — 2026-07-03
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `/sandpaper:release` — draft release notes and a semver bump from
|
|
16
|
+
`brain/log.html`, then `npm version` + tag + push.
|
|
17
|
+
- `bin/verify-publish.js` (`npm run verify-publish`) — tarball-safety gate:
|
|
18
|
+
no `site/`, no secrets, size envelope.
|
|
19
|
+
- `.github/workflows/ci.yml` — tests + verify-publish on every push/PR,
|
|
20
|
+
Node 18/20/22.
|
|
21
|
+
- `.github/workflows/release.yml` — a pushed tag runs tests → verify-publish
|
|
22
|
+
→ `npm publish --provenance` → a GitHub Release.
|
|
23
|
+
- `.github/dependabot.yml` — weekly Actions + npm dependency checks.
|
|
24
|
+
|
|
25
|
+
## [0.1.0] — 2026-07-03
|
|
26
|
+
|
|
27
|
+
Initial public release.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- The living brain: cover, three lenses (product/engineering/project), three books
|
|
31
|
+
(log/decisions/learnings), a canvas for elevated explanations.
|
|
32
|
+
- The refine-in-place toolbar (Sand / Hands / Sling) for local editing.
|
|
33
|
+
- `sandpaper install-skill` / `init` / `upgrade` / `rebuild` / `doctor` / `open` — the
|
|
34
|
+
zero-AI CLI plumbing.
|
|
35
|
+
- 12 `/sandpaper:*` slash commands — the Claude Code-side intelligence.
|
|
36
|
+
- Auto-updating hooks (SessionStart digest injection, Stop stamp-check).
|
|
37
|
+
- The out-link resolver — `brain/` is always publishable, detached or not.
|
|
38
|
+
- The cyanotype "as-built" identity, shared by the brain and the landing page.
|
|
39
|
+
- Published to npm as `@nynb/sandpaper` (`sandpaper` was taken; `sand-paper`
|
|
40
|
+
blocked by npm's anti-squatting policy — see `brain/decisions.html#d-npm-scope`).
|
|
41
|
+
|
|
42
|
+
[Unreleased]: https://github.com/codevalley/sandpaper/compare/v0.2.0...HEAD
|
|
43
|
+
[0.2.0]: https://github.com/codevalley/sandpaper/compare/v0.1.0...v0.2.0
|
|
44
|
+
[0.1.0]: https://github.com/codevalley/sandpaper/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -14,8 +14,8 @@ readable by you.
|
|
|
14
14
|
## Quick start
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
# in your repo
|
|
18
|
-
npx
|
|
17
|
+
# in your repo:
|
|
18
|
+
npx @nynb/sandpaper install-skill
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
Then, inside Claude Code:
|
|
@@ -27,10 +27,10 @@ Then, inside Claude Code:
|
|
|
27
27
|
And to view it:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
npx
|
|
30
|
+
npx @nynb/sandpaper open # serves the repo + opens brain/index.html
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
(
|
|
33
|
+
(Or install straight from GitHub, no npm account needed: `npx github:codevalley/sandpaper install-skill`.)
|
|
34
34
|
|
|
35
35
|
## What you get
|
|
36
36
|
|
|
@@ -77,6 +77,7 @@ skips hook wiring and prints the settings snippet instead.
|
|
|
77
77
|
- `/sandpaper:learn` — record a gotcha or verdict
|
|
78
78
|
- `/sandpaper:canvas` — elevate an explanation into a board on the cover's canvas
|
|
79
79
|
- `/sandpaper:sync` — reconcile the brain against the code; find and flag drift
|
|
80
|
+
- `/sandpaper:release` — cut a release: draft notes from the brain, pick a semver bump, tag, push
|
|
80
81
|
|
|
81
82
|
**Set up & run**
|
|
82
83
|
|
|
@@ -97,6 +98,29 @@ Two hooks keep the brain current without prodding. `install-skill` wires them in
|
|
|
97
98
|
nothing changed under `brain/`, it blocks once and asks the agent to stamp.
|
|
98
99
|
Self-limiting: it never loops, and the agent can decline by stopping again.
|
|
99
100
|
|
|
101
|
+
## Releasing (this repo's own npm package)
|
|
102
|
+
|
|
103
|
+
Versioning is a Sandpaper feature, not a side process: `/sandpaper:release` reads
|
|
104
|
+
`brain/log.html` since the last tag, proposes a semver bump with reasoning, drafts
|
|
105
|
+
`CHANGELOG.md` from what actually happened (not from scratch), then runs `npm version`
|
|
106
|
+
and `git push --follow-tags`. Pushing the tag hands off to
|
|
107
|
+
[`.github/workflows/release.yml`](.github/workflows/release.yml), which re-runs tests
|
|
108
|
+
and [`verify-publish`](bin/verify-publish.js) (the same tarball-safety checks — no
|
|
109
|
+
`site/`, no secrets, size within envelope), then `npm publish --provenance` and cuts a
|
|
110
|
+
GitHub Release from the changelog section.
|
|
111
|
+
|
|
112
|
+
CI publishing needs one of:
|
|
113
|
+
- an **`NPM_TOKEN`** repo secret — generate an **Automation** token from your npm
|
|
114
|
+
account (Access Tokens → Generate New Token → Automation). A regular Publish token
|
|
115
|
+
won't work if your account requires 2FA for writes — it still demands an interactive
|
|
116
|
+
OTP, which a CI runner can't provide.
|
|
117
|
+
- or npm **Trusted Publishing** (OIDC) configured for this exact repo + workflow, if
|
|
118
|
+
your account offers it — no token to store or rotate.
|
|
119
|
+
|
|
120
|
+
[`.github/workflows/ci.yml`](.github/workflows/ci.yml) runs the same tests + `verify-publish`
|
|
121
|
+
on every push and PR, across Node 18/20/22. [`.github/dependabot.yml`](.github/dependabot.yml)
|
|
122
|
+
keeps pinned Actions (and any future dependency) patched.
|
|
123
|
+
|
|
100
124
|
## Publishing the brain
|
|
101
125
|
|
|
102
126
|
`brain/` is always publishable — point GitHub Pages, Vercel, Netlify, or Cloudflare at
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// verify-publish.js — the gate before any npm publish (local or CI). Zero deps.
|
|
3
|
+
// Codifies the checks that used to be manual: the tarball ships only the tool
|
|
4
|
+
// (never site/ or brain content), stays within an expected size/file envelope
|
|
5
|
+
// (a silent jump is the first sign of an accidental inclusion), and carries no
|
|
6
|
+
// obvious secrets. Exit 0 = safe to publish; exit 1 = do not publish, read why.
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
|
|
10
|
+
const FORBIDDEN_PREFIXES = ['site/', 'test/', '.github/', '.sandpaper/', '.vercel/'];
|
|
11
|
+
const MAX_FILES = 45; // today: 30. A jump past this means something new is shipping — look before raising it.
|
|
12
|
+
const MAX_UNPACKED_KB = 400; // today: ~218 KB.
|
|
13
|
+
const SECRET_PATTERNS = [
|
|
14
|
+
/sk-[A-Za-z0-9]{20,}/, /ghp_[A-Za-z0-9]{20,}/, /AKIA[0-9A-Z]{16}/,
|
|
15
|
+
/-----BEGIN (RSA|OPENSSH|EC) PRIVATE KEY-----/,
|
|
16
|
+
/api[_-]?key\s*[:=]\s*['"][A-Za-z0-9]{10,}['"]/i,
|
|
17
|
+
/password\s*[:=]\s*['"][^'"]{4,}['"]/i,
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const bad = (m) => { console.error(' ✗ ' + m); process.exitCode = 1; };
|
|
21
|
+
const ok = (m) => console.log(' ✓ ' + m);
|
|
22
|
+
|
|
23
|
+
const pack = JSON.parse(execFileSync('npm', ['pack', '--dry-run', '--json'], { encoding: 'utf8' }))[0];
|
|
24
|
+
const files = pack.files.map((f) => f.path);
|
|
25
|
+
|
|
26
|
+
const forbidden = files.filter((f) => FORBIDDEN_PREFIXES.some((p) => f.startsWith(p)));
|
|
27
|
+
if (forbidden.length) bad(`forbidden paths in the tarball: ${forbidden.join(', ')}`);
|
|
28
|
+
else ok(`no forbidden paths (checked against ${FORBIDDEN_PREFIXES.join(', ')})`);
|
|
29
|
+
|
|
30
|
+
if (files.length > MAX_FILES) bad(`${files.length} files exceeds the expected envelope (${MAX_FILES}) — new files? review and raise MAX_FILES if intentional`);
|
|
31
|
+
else ok(`${files.length} files (within the ${MAX_FILES}-file envelope)`);
|
|
32
|
+
|
|
33
|
+
const unpackedKb = Math.round(pack.unpackedSize / 1024);
|
|
34
|
+
if (unpackedKb > MAX_UNPACKED_KB) bad(`${unpackedKb} KB unpacked exceeds the expected envelope (${MAX_UNPACKED_KB} KB)`);
|
|
35
|
+
else ok(`${unpackedKb} KB unpacked (within the ${MAX_UNPACKED_KB} KB envelope)`);
|
|
36
|
+
|
|
37
|
+
let hits = 0;
|
|
38
|
+
for (const f of files) {
|
|
39
|
+
let text;
|
|
40
|
+
try { text = readFileSync(f, 'utf8'); } catch { continue; } // binary or unreadable — skip
|
|
41
|
+
for (const re of SECRET_PATTERNS) if (re.test(text)) { bad(`possible secret in ${f} (matched ${re})`); hits++; }
|
|
42
|
+
}
|
|
43
|
+
if (!hits) ok('no secret patterns found');
|
|
44
|
+
|
|
45
|
+
if (process.exitCode) { console.error('\n ✗ verify-publish failed — do not run npm publish.\n'); }
|
|
46
|
+
else console.log('\n ✓ safe to publish.\n');
|
package/package.json
CHANGED
|
@@ -1,19 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nynb/sandpaper",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A living project brain + refine-in-place toolbar for Claude Code projects. npx @nynb/sandpaper install-skill / init / doctor / open.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
"bin": {
|
|
7
|
+
"sandpaper": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"public/",
|
|
13
|
+
"skill/",
|
|
14
|
+
"brain/assets/",
|
|
15
|
+
"README.md",
|
|
16
|
+
"CHANGELOG.md"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"claude-code",
|
|
20
|
+
"skill",
|
|
21
|
+
"project-brain",
|
|
22
|
+
"living-docs",
|
|
23
|
+
"documentation",
|
|
24
|
+
"agent"
|
|
25
|
+
],
|
|
9
26
|
"author": "Narayan <codevalley@live.com>",
|
|
10
27
|
"license": "MIT",
|
|
11
|
-
"repository": {
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/codevalley/sandpaper.git"
|
|
31
|
+
},
|
|
12
32
|
"homepage": "https://github.com/codevalley/sandpaper#readme",
|
|
13
33
|
"bugs": "https://github.com/codevalley/sandpaper/issues",
|
|
14
|
-
"engines": {
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
15
37
|
"scripts": {
|
|
16
38
|
"start": "node bin/cli.js help",
|
|
17
|
-
"test": "node test/parse-test.js && node test/markdown-test.js && node test/edit-test.js"
|
|
39
|
+
"test": "node test/parse-test.js && node test/markdown-test.js && node test/edit-test.js",
|
|
40
|
+
"verify-publish": "node bin/verify-publish.js"
|
|
18
41
|
}
|
|
19
42
|
}
|
package/skill/sandpaper/SKILL.md
CHANGED
|
@@ -105,7 +105,8 @@ you might forget and a system that cannot silently drift.
|
|
|
105
105
|
## Commands
|
|
106
106
|
`/help` (this list) · `/init` (harvest → interview → generate a brain) · `/stamp` (the 6-step
|
|
107
107
|
stamp) · `/canvas` (elevate an explanation into a board on the cover) · `/plan` · `/decide` ·
|
|
108
|
-
`/learn` · `/log` · `/sync` (reconcile + heal drift) ·
|
|
108
|
+
`/learn` · `/log` · `/sync` (reconcile + heal drift) · `/release` (draft notes from the brain,
|
|
109
|
+
pick a semver bump, tag, push) ·
|
|
109
110
|
`/open` (serve + open the brain in a browser) · `/serve` (on-page refine toolbar) · `/theme`
|
|
110
111
|
(re-skin from a brand hex). Run `/sandpaper:help` for the full grouped list.
|
|
111
112
|
|
|
@@ -12,6 +12,7 @@ Show the user the Sandpaper command set, grouped like this, then offer to run on
|
|
|
12
12
|
- `/sandpaper:learn` — record a gotcha or verdict learning
|
|
13
13
|
- `/sandpaper:canvas` — elevate an explanation into a board on the cover's canvas
|
|
14
14
|
- `/sandpaper:sync` — reconcile the brain against the code; find + heal drift
|
|
15
|
+
- `/sandpaper:release` — cut a release: draft notes from the brain, pick a semver bump, tag, push
|
|
15
16
|
|
|
16
17
|
**Set up & run**
|
|
17
18
|
- `/sandpaper:init` — scaffold a new brain for this repo (harvest → interview → generate)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Cut a release — draft notes from the brain, pick a semver bump, tag, push
|
|
3
|
+
argument-hint: "[optional: force patch | minor | major]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Cut a release of this package $ARGUMENTS. The brain already narrates every session — draft
|
|
7
|
+
from it, never from scratch.
|
|
8
|
+
|
|
9
|
+
1. **RANGE** — find the last release tag (`git describe --tags --abbrev=0`; none = the whole
|
|
10
|
+
history). Read every `brain/log.html` worklog entry since that tag's date, plus any
|
|
11
|
+
`brain/decisions.html` entries in range — that's the real source, richer than raw commit
|
|
12
|
+
messages.
|
|
13
|
+
|
|
14
|
+
2. **PROPOSE THE BUMP** — read the range for signal, then ask the owner to confirm (AskUserQuestion,
|
|
15
|
+
never assume silently):
|
|
16
|
+
- **major** — a breaking change: a removed/renamed CLI flag or command, an incompatible file
|
|
17
|
+
format change, anything that breaks an existing install.
|
|
18
|
+
- **minor** — new capability shipped (a new command, a new CLI flag, a new brain feature).
|
|
19
|
+
- **patch** — fixes, polish, docs, internal-only changes.
|
|
20
|
+
If `$ARGUMENTS` already names one, propose it pre-selected but still confirm — a human okays
|
|
21
|
+
every version bump.
|
|
22
|
+
|
|
23
|
+
3. **DRAFT THE NOTES** — write a `## [X.Y.Z] — YYYY-MM-DD` section for `CHANGELOG.md` in
|
|
24
|
+
Keep-a-Changelog style (`### Added` / `### Changed` / `### Fixed` / `### Security` as they
|
|
25
|
+
apply), one line per real change, in plain user-facing language — not log-entry jargon. Fold
|
|
26
|
+
in anything still under `## [Unreleased]`. Show the draft to the owner before writing it.
|
|
27
|
+
|
|
28
|
+
4. **WRITE + TAG** — insert the new section into `CHANGELOG.md` above the previous version (keep
|
|
29
|
+
`[Unreleased]` empty at the top), update the compare-links footer, then:
|
|
30
|
+
```
|
|
31
|
+
npm version <bump> -m "chore(release): v%s"
|
|
32
|
+
git push --follow-tags
|
|
33
|
+
```
|
|
34
|
+
`npm version` bumps `package.json`, commits, and tags `vX.Y.Z` in one step — don't hand-edit
|
|
35
|
+
the version number.
|
|
36
|
+
|
|
37
|
+
5. **STAMP** — run the rest of `/sandpaper:stamp`: a log row for the release, the cover's NOW,
|
|
38
|
+
the digest, and a plan-board tick if a task tracked this work.
|
|
39
|
+
|
|
40
|
+
6. **HANDOFF** — tell the owner the pushed tag will trigger `.github/workflows/release.yml`
|
|
41
|
+
(tests → `verify-publish` → `npm publish --provenance` → a GitHub Release), which needs either
|
|
42
|
+
an `NPM_TOKEN` repo secret (an npm **Automation** token — required if the account has
|
|
43
|
+
`auth-and-writes` 2FA, since only Automation tokens skip the interactive OTP prompt) or npm
|
|
44
|
+
Trusted Publishing configured for this repo + workflow. Never publish to npm directly from
|
|
45
|
+
here — that's the release workflow's job, and it verifies before it publishes.
|