@techgoblin/gobstack 0.0.0-stage → 0.4.4-beta.1
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 +351 -0
- package/LICENSE +21 -0
- package/README.md +163 -2
- package/VERSION +1 -0
- package/adapters/_template/adapter.tsv +16 -0
- package/adapters/_template/detect.sh +10 -0
- package/adapters/_template/emit.sh +5 -0
- package/adapters/_template/verify.sh +4 -0
- package/adapters/claude/adapter.tsv +8 -0
- package/adapters/claude/detect.sh +8 -0
- package/adapters/claude/verify.sh +47 -0
- package/adapters/codex/adapter.tsv +12 -0
- package/adapters/codex/detect.sh +9 -0
- package/adapters/codex/verify.sh +45 -0
- package/adapters/copilot/adapter.tsv +10 -0
- package/adapters/copilot/detect.sh +8 -0
- package/adapters/copilot/verify.sh +45 -0
- package/adapters/cursor/adapter.tsv +11 -0
- package/adapters/cursor/detect.sh +10 -0
- package/adapters/cursor/verify.sh +45 -0
- package/adapters/gemini/adapter.tsv +15 -0
- package/adapters/gemini/detect.sh +11 -0
- package/adapters/gemini/verify.sh +49 -0
- package/adapters/hermes/adapter.tsv +9 -0
- package/adapters/hermes/detect.sh +8 -0
- package/adapters/hermes/verify.sh +27 -0
- package/adapters/opencode/adapter.tsv +14 -0
- package/adapters/opencode/detect.sh +9 -0
- package/adapters/opencode/verify.sh +45 -0
- package/automations/README.md +53 -0
- package/automations/bugreporter-intake.sh +145 -0
- package/automations/drift-audit.sh +139 -0
- package/automations/report.schema.tsv +10 -0
- package/bans/README.md +82 -0
- package/bans/grep-ban.sh +84 -0
- package/bans/layer-check.sh +57 -0
- package/bin/goblin +119 -0
- package/bin/goblin-audit +145 -0
- package/bin/goblin-bans +178 -0
- package/bin/goblin-doctor +233 -0
- package/bin/goblin-emit +482 -0
- package/bin/goblin-init +519 -0
- package/bin/goblin-install +720 -0
- package/bin/goblin-lib.sh +289 -0
- package/bin/goblin-model +105 -0
- package/bin/goblin-upgrade +572 -0
- package/bin/goblin-verify +2798 -0
- package/bin/goblin.js +48 -0
- package/docs/ADOPTION.md +168 -0
- package/docs/CI.md +187 -0
- package/docs/CONTRACTS.md +197 -0
- package/docs/DESIGN.md +92 -0
- package/docs/ENFORCEMENT.md +225 -0
- package/docs/FLOWS.md +164 -0
- package/docs/GUARDRAILS.md +126 -0
- package/docs/GUIDE.md +610 -0
- package/docs/INTEGRATION.md +92 -0
- package/docs/LIMITS.md +591 -0
- package/docs/LOOP.md +165 -0
- package/docs/RE-PLAYBOOK.md +183 -0
- package/docs/RISKS.md +70 -0
- package/docs/ROLES.md +105 -0
- package/manifest/bans.tsv +9 -0
- package/manifest/classes.tsv +61 -0
- package/manifest/enforcement.tsv +88 -0
- package/manifest/glossary.tsv +25 -0
- package/manifest/playbooks.tsv +16 -0
- package/package.json +36 -4
- package/presets/A-shipped-software.yaml +48 -0
- package/presets/B-service-config.yaml +40 -0
- package/presets/C-game.yaml +38 -0
- package/presets/D-knowledge.yaml +41 -0
- package/presets/E-fleet-config.yaml +42 -0
- package/presets/F-electron.yaml +67 -0
- package/roles.yaml +54 -0
- package/skills/goblin-bootstrap/SKILL.md +51 -0
- package/skills/goblin-bugfix/SKILL.md +26 -0
- package/skills/goblin-bugreporter/SKILL.md +52 -0
- package/skills/goblin-drift-audit/SKILL.md +43 -0
- package/skills/goblin-eval/SKILL.md +68 -0
- package/skills/goblin-feature/SKILL.md +26 -0
- package/skills/goblin-feature-map/SKILL.md +140 -0
- package/skills/goblin-handoff/SKILL.md +28 -0
- package/skills/goblin-investigation/SKILL.md +26 -0
- package/skills/goblin-judge/SKILL.md +74 -0
- package/skills/goblin-loop/SKILL.md +88 -0
- package/skills/goblin-mode/SKILL.md +70 -0
- package/skills/goblin-overnight/SKILL.md +42 -0
- package/skills/goblin-pr-gate/SKILL.md +42 -0
- package/skills/goblin-re-mobile/SKILL.md +51 -0
- package/skills/goblin-refactor/SKILL.md +23 -0
- package/skills/goblin-sweep/SKILL.md +23 -0
- package/skills/goblin-tdd-repro/SKILL.md +27 -0
- package/skills/goblin-verify-author/SKILL.md +50 -0
- package/skills/practice/SKILL.md +37 -0
- package/templates/AGENTS.md.tmpl +23 -0
- package/templates/HANDOFF.md.tmpl +43 -0
- package/templates/SPEC.md.tmpl +34 -0
- package/templates/audit-waiver.tsv.tmpl +10 -0
- package/templates/boundary-waivers.tmpl +8 -0
- package/templates/checks/assert.mjs.tmpl +60 -0
- package/templates/checks/gate.sh.tmpl +29 -0
- package/templates/ci/goblin-gate.yml.tmpl +46 -0
- package/templates/goblin.yaml.tmpl +138 -0
- package/templates/install-hooks.allowlist.tmpl +9 -0
- package/templates/loop/decisions.tsv.tmpl +1 -0
- package/templates/loop/predicate.tmpl +16 -0
- package/templates/report.yaml.tmpl +16 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# The guard rails - the security and perf rows
|
|
2
|
+
|
|
3
|
+
`manifest/enforcement.tsv` is the machine-readable form; this is the prose for the ten rows it
|
|
4
|
+
gained in v0.2 (`SC-01`..`SC-09`, `PF-01`). Every one of them is **declared** in
|
|
5
|
+
`.goblin/goblin.yaml` under `security:` and `perf:` - a stack-specific rule guessed from the
|
|
6
|
+
files on disk is how a matrix starts lying, so nothing here infers a stack.
|
|
7
|
+
|
|
8
|
+
## The rung ladder, and the rule for choosing a rung
|
|
9
|
+
|
|
10
|
+
(a) lint rule / static check > (b) script gate > (c) CI job > (d) runtime check > (e) prose
|
|
11
|
+
|
|
12
|
+
The rung is chosen by **what can observe the failure**, not by what is cheapest to write, and the
|
|
13
|
+
rung below must be *demonstrably unable* to see it - the reason is recorded in the row's
|
|
14
|
+
`if_not_why` cell. Three constraints shaped the design, and all three are pre-existing:
|
|
15
|
+
|
|
16
|
+
1. **No network at verify time.** `docs/RISKS.md` K4: a network call at verify time breaks the
|
|
17
|
+
offline dependency contract. So `SC-07` reads a **recorded** audit; recording is a separate,
|
|
18
|
+
deliberate command (`.goblin/bin/goblin-audit`). A row that needs the network is not a
|
|
19
|
+
verify-time row.
|
|
20
|
+
2. **`enforced_by` is a closed enum** (`script`, `lint`, `gate`, `advisory`) and `check` is one of:
|
|
21
|
+
a real command, the literal `advisory`, or `goblin-verify --only <ID>` for a multi-line body.
|
|
22
|
+
Every row here obeys that, and `IN-03` fails the manifest otherwise.
|
|
23
|
+
3. **`advisory_ceiling` is 10 and the count was 8.** This design spends **one** slot (`SC-09`),
|
|
24
|
+
which took the count to 9 of 10. **Corrected 2026-09-25 (AB3):** it is **10 of 10** now —
|
|
25
|
+
`JG-03` took the tenth slot in W3, and the run prints `advisory 10 of ceiling 10 (0 free
|
|
26
|
+
slots: the next advisory row FAILs)`. Nothing else in this lane is prose dressed as a check.
|
|
27
|
+
|
|
28
|
+
## T1 - secrets and the config surface (`SC-01`..`SC-04`)
|
|
29
|
+
|
|
30
|
+
All four are rung (a)/(b): one shell line or a small builtin, and all four are RED-able.
|
|
31
|
+
|
|
32
|
+
| row | what it proves | how it proves it |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `SC-01` | no secret file is tracked | `git ls-files` over the `.env`/`.pem`/`.key` family (`.example`/`.sample`/`.template` excluded) |
|
|
35
|
+
| `SC-02` | the ignore rules cover the **whole** family | clause 1 reads `.gitignore`; clause 2 asks git's own matcher, `git check-ignore -q`, once per path - a rule that looks right but does not match still fails |
|
|
36
|
+
| `SC-03` | no client-visible name is secret-shaped, and no build output carries a secret literal | the `NEXT_PUBLIC_*_(SECRET\|TOKEN\|KEY\|PASSWORD\|PRIVATE)` name pattern over source, then known secret prefixes (`sk-`, `ghp_`, `AKIA`, `eyJ`) over `security.build_output` |
|
|
37
|
+
| `SC-04` | every cookie write carries its flags | `document.cookie =` needs `secure` + `samesite` in the same statement; `cookies().set(` / `res.cookie(` need `httpOnly` + `sameSite` |
|
|
38
|
+
|
|
39
|
+
`SC-02` ships a real control rather than being assumed, because the honest baseline is GREEN: a
|
|
40
|
+
fresh install writes the family into `.gitignore` (`sec_gitignore_family: yes`), and the control
|
|
41
|
+
proves the row would notice the day a hand edit narrows it.
|
|
42
|
+
|
|
43
|
+
**A JS-written cookie is readable by any script.** `SC-04` *reports* that; it does not fail on it.
|
|
44
|
+
That is a design fact about the product, not a bug, and a matrix that failed on it would be wrong
|
|
45
|
+
about the thing it was measuring.
|
|
46
|
+
|
|
47
|
+
## T2 - input boundaries and dependencies (`SC-05`..`SC-08`)
|
|
48
|
+
|
|
49
|
+
| row | what it proves | the limit it states |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `SC-05` | every write route calls a validator, or is waived | it proves a validator is *called* (`safeParse\|zod\|valibot\|yup\|ajv\|superstruct\|validate(`), never that the schema is right - a schema that accepts everything passes |
|
|
52
|
+
| `SC-06` | a lockfile exists and is tracked | it cannot see that the lockfile is *stale* relative to `package.json`: resolving that needs the package manager, which is a deliberate network-shaped step |
|
|
53
|
+
| `SC-07` | the audit record exists, is dated, is fresh, and every high\|critical line is waived with its own dated reason | it cannot see an advisory the registry did not know on the day the record was taken, and it never calls the registry itself |
|
|
54
|
+
| `SC-08` | no dependency runs an install-time script outside the allowlist | `pnpm`/`yarn` lockfiles carry no `hasInstallScript` field, so those repos get a SKIP with that reason rather than a vacuous pass |
|
|
55
|
+
|
|
56
|
+
`SC-07` prints its waiver count on the gate line (`N high|critical line(s), W matched waiver(s), U
|
|
57
|
+
unwaived`), reusing `DS-02`'s annotation pattern, so **the debt is visible on every run** and the
|
|
58
|
+
row can pass while the debt stays loud.
|
|
59
|
+
|
|
60
|
+
`.goblin/bin/goblin-audit` is the one tool in the toolchain allowed to touch the network, and it
|
|
61
|
+
is not a check: a human runs it, once, deliberately, and commits `.goblin/audit.tsv`. Its exit
|
|
62
|
+
codes are part of the contract:
|
|
63
|
+
|
|
64
|
+
| exit | meaning |
|
|
65
|
+
|---|---|
|
|
66
|
+
| 0 | the record was written (clean or not) |
|
|
67
|
+
| 2 | usage, or no `.goblin/goblin.yaml` to read `security.audit_cmd` from |
|
|
68
|
+
| 3 | the class declares no audit command - nothing to run |
|
|
69
|
+
| 4 | the declared command could not run |
|
|
70
|
+
| 5 | the output could not be parsed as an audit report, and it **refuses to write a record**: an empty record reads to `SC-07` as "clean", which would be a fabricated pass |
|
|
71
|
+
|
|
72
|
+
`SC-08` is the lowest-value row of the ten and the first to cut if the matrix gets heavy: it is
|
|
73
|
+
regression detection, not a live finding. It is in because an install hook is arbitrary code that
|
|
74
|
+
runs on every `npm ci`.
|
|
75
|
+
|
|
76
|
+
## T3 - the performance budget (`PF-01`, plus the ratchet)
|
|
77
|
+
|
|
78
|
+
The budget **is** `GT-04`/`GT-05`, unchanged: `ratchet.name` is the metric, `ratchet.cmd` is the
|
|
79
|
+
one command that produces it, `ratchet.ceiling` is the measured baseline, and `GT-05` prints
|
|
80
|
+
`old <n> + <delta> new = <n>` when the number rises. **No second mechanism, no new key for the
|
|
81
|
+
number.**
|
|
82
|
+
|
|
83
|
+
| class | metric | command | hermetic? |
|
|
84
|
+
|---|---|---|---|
|
|
85
|
+
| A - web app (Next) | total client JS bytes | `find .next/static -type f -name '*.js' -exec cat {} + \| wc -c` | yes |
|
|
86
|
+
| A - SPA (Vite) | bundle JS bytes | `find dist/assets -type f -name '*.js' -exec cat {} + \| wc -c` | yes |
|
|
87
|
+
| A - component lib | published bytes | `find dist -type f -exec cat {} + \| wc -c` | yes |
|
|
88
|
+
| B - service/config | own build bytes, plus host latency | `find dist -type f -exec cat {} + \| wc -c` · `curl -w '%{time_total}'` | size yes, latency **no** |
|
|
89
|
+
| C - game | build bytes, plus host frame time | as A · Editor run | size yes, frame time **no** |
|
|
90
|
+
| D, E | none declared | - | - |
|
|
91
|
+
|
|
92
|
+
`find ... -exec cat {} + | wc -c` is used rather than `du` because `du` reports block sizes and is
|
|
93
|
+
not deterministic across filesystems. Raw bytes are the *reported* number and are the only one
|
|
94
|
+
that is ratcheted: a gzip figure changes with the compressor, so it is a report, never a ceiling.
|
|
95
|
+
|
|
96
|
+
**The class-A preset ships this metric** (`client_js_bytes`), and the TODO count that used to be
|
|
97
|
+
the ratchet moved into a **gate** (`todo_ceiling`, `-le 160`): a shipped-software round is
|
|
98
|
+
supposed to move the perf number, and the TODO ceiling is a floor against decay, not the budget.
|
|
99
|
+
A fresh install measures `0` for a repo with no build output, so the number is honest from the
|
|
100
|
+
first commit and gets re-anchored deliberately.
|
|
101
|
+
|
|
102
|
+
**Re-anchoring is deliberate, never automatic.** When a round legitimately raises the number, the
|
|
103
|
+
operator re-runs the command, writes the new `ceiling`, and writes the matching
|
|
104
|
+
`perf.baseline_value`/`baseline_commit`/`measured`. `PF-01` is the row that stops "I raised the
|
|
105
|
+
ceiling" from silently becoming "I never measured again": a baseline whose commit does not resolve
|
|
106
|
+
to an ancestor of `HEAD` is a RED.
|
|
107
|
+
|
|
108
|
+
`PF-01` **skips with a reason** when the class declares no metric (`perf.metric` empty) or when no
|
|
109
|
+
baseline has been recorded yet - because a row that is RED on every fresh install teaches people
|
|
110
|
+
to ignore it. The skip names the command to run.
|
|
111
|
+
|
|
112
|
+
## What this lane cannot see
|
|
113
|
+
|
|
114
|
+
- **A local byte count is not a real user's device.** It says nothing about parse/execute time on
|
|
115
|
+
a mid-range phone, a cold cache or a slow network - and nothing about what the bytes do. A
|
|
116
|
+
700 KB bundle that does nothing is better than a 200 KB one that blocks the main thread.
|
|
117
|
+
- **Frame time, idle CPU, memory growth and latency are host gates**, named as such in
|
|
118
|
+
`perf.host_gate` and in `gates:` - never hermetic ratchets.
|
|
119
|
+
- **A perf budget cannot see a layout thrash or a re-render per keystroke.** Only a frame-time
|
|
120
|
+
measurement can, and that is a host gate.
|
|
121
|
+
- **No automation or audit has ever run against a real registry here.** Cost per run, and whether
|
|
122
|
+
the recorded waiver set matches the real advisory set, are unmeasured; the first real
|
|
123
|
+
`goblin-audit` is what produces those numbers.
|
|
124
|
+
- **The record's parser reads npm's JSON by field name.** A different audit tool with a different
|
|
125
|
+
shape is refused (exit 5) rather than silently recorded as clean - which is the safe failure,
|
|
126
|
+
but it is still a failure.
|