@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.
Files changed (108) hide show
  1. package/CHANGELOG.md +351 -0
  2. package/LICENSE +21 -0
  3. package/README.md +163 -2
  4. package/VERSION +1 -0
  5. package/adapters/_template/adapter.tsv +16 -0
  6. package/adapters/_template/detect.sh +10 -0
  7. package/adapters/_template/emit.sh +5 -0
  8. package/adapters/_template/verify.sh +4 -0
  9. package/adapters/claude/adapter.tsv +8 -0
  10. package/adapters/claude/detect.sh +8 -0
  11. package/adapters/claude/verify.sh +47 -0
  12. package/adapters/codex/adapter.tsv +12 -0
  13. package/adapters/codex/detect.sh +9 -0
  14. package/adapters/codex/verify.sh +45 -0
  15. package/adapters/copilot/adapter.tsv +10 -0
  16. package/adapters/copilot/detect.sh +8 -0
  17. package/adapters/copilot/verify.sh +45 -0
  18. package/adapters/cursor/adapter.tsv +11 -0
  19. package/adapters/cursor/detect.sh +10 -0
  20. package/adapters/cursor/verify.sh +45 -0
  21. package/adapters/gemini/adapter.tsv +15 -0
  22. package/adapters/gemini/detect.sh +11 -0
  23. package/adapters/gemini/verify.sh +49 -0
  24. package/adapters/hermes/adapter.tsv +9 -0
  25. package/adapters/hermes/detect.sh +8 -0
  26. package/adapters/hermes/verify.sh +27 -0
  27. package/adapters/opencode/adapter.tsv +14 -0
  28. package/adapters/opencode/detect.sh +9 -0
  29. package/adapters/opencode/verify.sh +45 -0
  30. package/automations/README.md +53 -0
  31. package/automations/bugreporter-intake.sh +145 -0
  32. package/automations/drift-audit.sh +139 -0
  33. package/automations/report.schema.tsv +10 -0
  34. package/bans/README.md +82 -0
  35. package/bans/grep-ban.sh +84 -0
  36. package/bans/layer-check.sh +57 -0
  37. package/bin/goblin +119 -0
  38. package/bin/goblin-audit +145 -0
  39. package/bin/goblin-bans +178 -0
  40. package/bin/goblin-doctor +233 -0
  41. package/bin/goblin-emit +482 -0
  42. package/bin/goblin-init +519 -0
  43. package/bin/goblin-install +720 -0
  44. package/bin/goblin-lib.sh +289 -0
  45. package/bin/goblin-model +105 -0
  46. package/bin/goblin-upgrade +572 -0
  47. package/bin/goblin-verify +2798 -0
  48. package/bin/goblin.js +48 -0
  49. package/docs/ADOPTION.md +168 -0
  50. package/docs/CI.md +187 -0
  51. package/docs/CONTRACTS.md +197 -0
  52. package/docs/DESIGN.md +92 -0
  53. package/docs/ENFORCEMENT.md +225 -0
  54. package/docs/FLOWS.md +164 -0
  55. package/docs/GUARDRAILS.md +126 -0
  56. package/docs/GUIDE.md +610 -0
  57. package/docs/INTEGRATION.md +92 -0
  58. package/docs/LIMITS.md +591 -0
  59. package/docs/LOOP.md +165 -0
  60. package/docs/RE-PLAYBOOK.md +183 -0
  61. package/docs/RISKS.md +70 -0
  62. package/docs/ROLES.md +105 -0
  63. package/manifest/bans.tsv +9 -0
  64. package/manifest/classes.tsv +61 -0
  65. package/manifest/enforcement.tsv +88 -0
  66. package/manifest/glossary.tsv +25 -0
  67. package/manifest/playbooks.tsv +16 -0
  68. package/package.json +36 -4
  69. package/presets/A-shipped-software.yaml +48 -0
  70. package/presets/B-service-config.yaml +40 -0
  71. package/presets/C-game.yaml +38 -0
  72. package/presets/D-knowledge.yaml +41 -0
  73. package/presets/E-fleet-config.yaml +42 -0
  74. package/presets/F-electron.yaml +67 -0
  75. package/roles.yaml +54 -0
  76. package/skills/goblin-bootstrap/SKILL.md +51 -0
  77. package/skills/goblin-bugfix/SKILL.md +26 -0
  78. package/skills/goblin-bugreporter/SKILL.md +52 -0
  79. package/skills/goblin-drift-audit/SKILL.md +43 -0
  80. package/skills/goblin-eval/SKILL.md +68 -0
  81. package/skills/goblin-feature/SKILL.md +26 -0
  82. package/skills/goblin-feature-map/SKILL.md +140 -0
  83. package/skills/goblin-handoff/SKILL.md +28 -0
  84. package/skills/goblin-investigation/SKILL.md +26 -0
  85. package/skills/goblin-judge/SKILL.md +74 -0
  86. package/skills/goblin-loop/SKILL.md +88 -0
  87. package/skills/goblin-mode/SKILL.md +70 -0
  88. package/skills/goblin-overnight/SKILL.md +42 -0
  89. package/skills/goblin-pr-gate/SKILL.md +42 -0
  90. package/skills/goblin-re-mobile/SKILL.md +51 -0
  91. package/skills/goblin-refactor/SKILL.md +23 -0
  92. package/skills/goblin-sweep/SKILL.md +23 -0
  93. package/skills/goblin-tdd-repro/SKILL.md +27 -0
  94. package/skills/goblin-verify-author/SKILL.md +50 -0
  95. package/skills/practice/SKILL.md +37 -0
  96. package/templates/AGENTS.md.tmpl +23 -0
  97. package/templates/HANDOFF.md.tmpl +43 -0
  98. package/templates/SPEC.md.tmpl +34 -0
  99. package/templates/audit-waiver.tsv.tmpl +10 -0
  100. package/templates/boundary-waivers.tmpl +8 -0
  101. package/templates/checks/assert.mjs.tmpl +60 -0
  102. package/templates/checks/gate.sh.tmpl +29 -0
  103. package/templates/ci/goblin-gate.yml.tmpl +46 -0
  104. package/templates/goblin.yaml.tmpl +138 -0
  105. package/templates/install-hooks.allowlist.tmpl +9 -0
  106. package/templates/loop/decisions.tsv.tmpl +1 -0
  107. package/templates/loop/predicate.tmpl +16 -0
  108. 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.