hstack 0.16.0 → 0.18.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.
Files changed (134) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +76 -163
  3. package/VERSION +1 -1
  4. package/dist/commands/doctor.js +18 -8
  5. package/dist/commands/doctor.js.map +1 -1
  6. package/dist/commands/init.js +7 -3
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/update.js +9 -2
  9. package/dist/commands/update.js.map +1 -1
  10. package/dist/lib/descriptions.js +10 -17
  11. package/dist/lib/descriptions.js.map +1 -1
  12. package/dist/lib/wire.js +166 -138
  13. package/dist/lib/wire.js.map +1 -1
  14. package/dist/manifest.js +41 -29
  15. package/dist/manifest.js.map +1 -1
  16. package/package.json +3 -4
  17. package/template/.claude/agents/adversarial-reviewer.md +44 -50
  18. package/template/.claude/agents/app-architect.md +44 -96
  19. package/template/.claude/agents/data-architect.md +46 -89
  20. package/template/.claude/agents/test-strategist.md +39 -68
  21. package/template/.claude/skills/hstack-adr-new/SKILL.md +23 -73
  22. package/template/.claude/skills/hstack-adversarial-review/SKILL.md +46 -152
  23. package/template/.claude/skills/hstack-adversarial-review/references/finding-categories.md +89 -103
  24. package/template/.claude/skills/hstack-app-architecture/SKILL.md +43 -68
  25. package/template/.claude/skills/hstack-commit/SKILL.md +27 -68
  26. package/template/.claude/skills/hstack-data-architecture/SKILL.md +34 -56
  27. package/template/.claude/skills/hstack-promote/SKILL.md +55 -0
  28. package/template/.claude/skills/hstack-story/SKILL.md +58 -0
  29. package/template/.claude/skills/hstack-test-audit/SKILL.md +64 -0
  30. package/template/.claude/skills/hstack-wrap/SKILL.md +50 -0
  31. package/template/.claude/skills/hstack-wrap/references/pr-description.md +45 -0
  32. package/template/KERNEL.md +75 -318
  33. package/template/templates/adr.md +2 -18
  34. package/template/templates/app-architecture.md +38 -50
  35. package/template/templates/code-standards.md +107 -0
  36. package/template/templates/data-architecture.md +38 -89
  37. package/template/templates/eslint-clean-code.mjs +322 -0
  38. package/template/templates/infrastructure.md +54 -148
  39. package/template/templates/roadmap.md +6 -12
  40. package/template/templates/story.md +7 -13
  41. package/template/templates/tech-debt.md +2 -37
  42. package/template/templates/tech-stack.md +2 -12
  43. package/template/.claude/agents/data-specialist.md +0 -81
  44. package/template/.claude/agents/implementer.md +0 -97
  45. package/template/.claude/agents/kernel-fit-analyst.md +0 -129
  46. package/template/.claude/agents/planner.md +0 -75
  47. package/template/.claude/agents/product-discovery.md +0 -129
  48. package/template/.claude/agents/product-manager.md +0 -76
  49. package/template/.claude/agents/researcher.md +0 -87
  50. package/template/.claude/agents/security-reviewer.md +0 -88
  51. package/template/.claude/agents/spec-author.md +0 -89
  52. package/template/.claude/agents/stack-architect.md +0 -108
  53. package/template/.claude/agents/ui-ux-briefer.md +0 -75
  54. package/template/.claude/agents/verifier.md +0 -81
  55. package/template/.claude/skills/hstack-branch/SKILL.md +0 -76
  56. package/template/.claude/skills/hstack-brownfield-init/SKILL.md +0 -131
  57. package/template/.claude/skills/hstack-change-new/SKILL.md +0 -84
  58. package/template/.claude/skills/hstack-change-plan/SKILL.md +0 -108
  59. package/template/.claude/skills/hstack-configure/SKILL.md +0 -99
  60. package/template/.claude/skills/hstack-coord/SKILL.md +0 -95
  61. package/template/.claude/skills/hstack-data-review/SKILL.md +0 -93
  62. package/template/.claude/skills/hstack-finalize/SKILL.md +0 -140
  63. package/template/.claude/skills/hstack-flag/SKILL.md +0 -99
  64. package/template/.claude/skills/hstack-greenfield-init/SKILL.md +0 -148
  65. package/template/.claude/skills/hstack-help/SKILL.md +0 -153
  66. package/template/.claude/skills/hstack-implement/SKILL.md +0 -155
  67. package/template/.claude/skills/hstack-kernel-fit-promote/SKILL.md +0 -125
  68. package/template/.claude/skills/hstack-kernel-fit-scan/SKILL.md +0 -142
  69. package/template/.claude/skills/hstack-kernel-fit-scan/references/slack-setup.md +0 -42
  70. package/template/.claude/skills/hstack-kernel-fit-triage/SKILL.md +0 -121
  71. package/template/.claude/skills/hstack-module-spec/SKILL.md +0 -78
  72. package/template/.claude/skills/hstack-product-discovery/SKILL.md +0 -75
  73. package/template/.claude/skills/hstack-research/SKILL.md +0 -115
  74. package/template/.claude/skills/hstack-scaffold/SKILL.md +0 -113
  75. package/template/.claude/skills/hstack-security-review/SKILL.md +0 -95
  76. package/template/.claude/skills/hstack-ship/SKILL.md +0 -119
  77. package/template/.claude/skills/hstack-stack-decide/SKILL.md +0 -81
  78. package/template/.claude/skills/hstack-story-draft/SKILL.md +0 -92
  79. package/template/.claude/skills/hstack-tech-debt-new/SKILL.md +0 -95
  80. package/template/.claude/skills/hstack-tech-debt-resolve/SKILL.md +0 -123
  81. package/template/.claude/skills/hstack-tech-debt-stale/SKILL.md +0 -86
  82. package/template/.claude/skills/hstack-tech-debt-wontfix/SKILL.md +0 -77
  83. package/template/.claude/skills/hstack-telemetry/SKILL.md +0 -73
  84. package/template/.claude/skills/hstack-test-plan/SKILL.md +0 -159
  85. package/template/.claude/skills/hstack-ui-brief/SKILL.md +0 -81
  86. package/template/.claude/skills/hstack-verify/SKILL.md +0 -128
  87. package/template/scripts/compute-merge-readiness.mjs +0 -780
  88. package/template/scripts/coord/coord_scan.py +0 -681
  89. package/template/scripts/run-gates.sh +0 -388
  90. package/template/scripts/telemetry/__init__.py +0 -6
  91. package/template/scripts/telemetry/insights/__init__.py +0 -0
  92. package/template/scripts/telemetry/insights/contract_drift.py +0 -137
  93. package/template/scripts/telemetry/insights/kernel_fit.py +0 -438
  94. package/template/scripts/telemetry/insights/overengineering.py +0 -115
  95. package/template/scripts/telemetry/insights/quality_outcomes.py +0 -131
  96. package/template/scripts/telemetry/insights/token_economics.py +0 -302
  97. package/template/scripts/telemetry/insights/workflow_shape.py +0 -198
  98. package/template/scripts/telemetry/parsers/__init__.py +0 -0
  99. package/template/scripts/telemetry/parsers/bodies.py +0 -87
  100. package/template/scripts/telemetry/parsers/commits.py +0 -219
  101. package/template/scripts/telemetry/parsers/frontmatter.py +0 -322
  102. package/template/scripts/telemetry/parsers/sidecars.py +0 -61
  103. package/template/scripts/telemetry/parsers/transcripts.py +0 -294
  104. package/template/scripts/telemetry/render.py +0 -460
  105. package/template/scripts/telemetry/report.py +0 -150
  106. package/template/scripts/telemetry/run_kernel_fit.py +0 -95
  107. package/template/scripts/telemetry/session_id.py +0 -139
  108. package/template/scripts/validate-spec.mjs +0 -3303
  109. package/template/templates/adversarial-review.md +0 -54
  110. package/template/templates/bootstrap.md +0 -105
  111. package/template/templates/change-spec.md +0 -82
  112. package/template/templates/ci-cd.md +0 -41
  113. package/template/templates/coord-message.md +0 -56
  114. package/template/templates/data-review.md +0 -54
  115. package/template/templates/discovery/brainstorm.md +0 -69
  116. package/template/templates/discovery/forcing-questions.md +0 -76
  117. package/template/templates/discovery/project-brief.md +0 -53
  118. package/template/templates/figma-handoff.md +0 -38
  119. package/template/templates/glossary.md +0 -20
  120. package/template/templates/hardening-checklist.md +0 -73
  121. package/template/templates/incident-runbook.md +0 -57
  122. package/template/templates/kernel-fit-finding.md +0 -63
  123. package/template/templates/kernel-fit-flag.md +0 -52
  124. package/template/templates/module-spec.md +0 -49
  125. package/template/templates/persona.md +0 -38
  126. package/template/templates/plan.md +0 -53
  127. package/template/templates/product-brief.md +0 -56
  128. package/template/templates/security-review.md +0 -63
  129. package/template/templates/telemetry-sidecar.md +0 -227
  130. package/template/templates/test-plan.md +0 -119
  131. package/template/templates/threat-model.md +0 -54
  132. package/template/templates/ui-brief.md +0 -49
  133. package/template/templates/verification.md +0 -63
  134. package/template/templates/vision.md +0 -34
@@ -0,0 +1,322 @@
1
+ /**
2
+ * hstack — clean-code ESLint reference config.
3
+ *
4
+ * The mechanical half of `hstack/context/code-standards.md`: everything a
5
+ * linter can measure lives here, at `error`, so `lint` fails instead of
6
+ * warning. A `warn` is a message an agent learns to scroll past; an `error`
7
+ * is a message it has to act on. Nothing here is a style preference — each
8
+ * rule maps to a way agent-written code has actually gone wrong.
9
+ *
10
+ * Usage, from the consumer's `eslint.config.mjs`:
11
+ *
12
+ * import { defineConfig } from 'eslint/config'
13
+ * import { cleanCode } from './hstack/templates/eslint-clean-code.mjs'
14
+ *
15
+ * export default defineConfig([
16
+ * ...yourFrameworkConfig, // e.g. eslint-config-next, which registers
17
+ * // the @typescript-eslint and import plugins
18
+ * ...cleanCode({
19
+ * typescript: true, // rules that need @typescript-eslint registered
20
+ * imports: true, // rules that need eslint-plugin-import registered
21
+ * supabase: true, // the unread-`error` rule (see below)
22
+ * modules: {
23
+ * logger: 'lib/observability/logger',
24
+ * config: 'lib/env',
25
+ * dbClient: 'lib/supabase',
26
+ * },
27
+ * }),
28
+ * ])
29
+ *
30
+ * The ratchet. Turning these on against an existing codebase fails `lint`
31
+ * hundreds of times at once. Freeze the current violations once —
32
+ *
33
+ * npx eslint --suppress-all
34
+ *
35
+ * — which writes `eslint-suppressions.json`. From then on only *new*
36
+ * violations fail; touching a file that carries old ones is the moment to
37
+ * pay them down (`npx eslint --prune-suppressions` drops the entries that no
38
+ * longer apply). Commit the suppressions file. The count only goes down.
39
+ *
40
+ * This file is framework-owned: `hstack update` overwrites it. Adjust
41
+ * thresholds through the `thresholds` option, not by editing here.
42
+ */
43
+
44
+ const DEFAULT_FILES = ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']
45
+ const DEFAULT_TEST_FILES = [
46
+ '**/*.test.{ts,tsx,js,jsx}',
47
+ '**/*.spec.{ts,tsx,js,jsx}',
48
+ '**/__tests__/**',
49
+ '**/e2e/**',
50
+ ]
51
+ const DEFAULT_SCRIPT_FILES = ['scripts/**']
52
+
53
+ const DEFAULT_THRESHOLDS = {
54
+ /** Lines per function, blank lines and comments excluded. */
55
+ functionLines: 60,
56
+ /** Lines per file, blank lines and comments excluded. */
57
+ fileLines: 400,
58
+ /** Independent paths through a function (if / for / && / ?: each add one). */
59
+ complexity: 15,
60
+ /** Nested blocks. Four is already a function that wants splitting. */
61
+ depth: 4,
62
+ /** Positional parameters. Past three, pass a named object. */
63
+ params: 3,
64
+ /** Nested callbacks. */
65
+ callbacks: 3,
66
+ }
67
+
68
+ /**
69
+ * Supabase returns `{ data, error }` and never throws. Code that takes `data`
70
+ * and ignores `error` reads as correct and fails silently — an RLS denial
71
+ * becomes an HTTP 200 that lies. This rule flags an awaited query-builder
72
+ * chain (`.from(…)`, `.rpc(…)`, `.storage.from(…)`) whose result is
73
+ * discarded, destructured without `error`, or read through `.data` only.
74
+ * Chaining `.throwOnError()` turns the result into an exception and satisfies
75
+ * the rule.
76
+ */
77
+ const supabaseUnreadErrorRule = {
78
+ meta: {
79
+ type: 'problem',
80
+ docs: {
81
+ description:
82
+ 'Require the `error` half of a Supabase `{ data, error }` result to be read.',
83
+ },
84
+ schema: [],
85
+ messages: {
86
+ unreadError:
87
+ 'The Supabase client never throws; it returns `{ data, error }`. Read `error` here ' +
88
+ '(or chain `.throwOnError()`). An unread error is a silent failure.',
89
+ discardedResult:
90
+ 'The result of this Supabase call is discarded, so a failed write goes unnoticed. ' +
91
+ 'Read `error` (or chain `.throwOnError()`).',
92
+ },
93
+ },
94
+ create(context) {
95
+ const QUERY_ROOTS = new Set(['from', 'rpc'])
96
+
97
+ /** Walks a call chain and reports whether it starts a Supabase query and whether it throws on error. */
98
+ function describeChain(node) {
99
+ let startsQuery = false
100
+ let throwsOnError = false
101
+ let current = node
102
+ while (current) {
103
+ if (current.type === 'CallExpression') {
104
+ const callee = current.callee
105
+ if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier') {
106
+ if (QUERY_ROOTS.has(callee.property.name)) startsQuery = true
107
+ if (callee.property.name === 'throwOnError') throwsOnError = true
108
+ }
109
+ current = callee
110
+ } else if (current.type === 'MemberExpression') {
111
+ current = current.object
112
+ } else {
113
+ break
114
+ }
115
+ }
116
+ return { startsQuery, throwsOnError }
117
+ }
118
+
119
+ function patternReadsError(pattern) {
120
+ return pattern.properties.some(
121
+ (property) =>
122
+ property.type === 'RestElement' ||
123
+ (property.key && property.key.type === 'Identifier' && property.key.name === 'error'),
124
+ )
125
+ }
126
+
127
+ return {
128
+ AwaitExpression(node) {
129
+ const { startsQuery, throwsOnError } = describeChain(node.argument)
130
+ if (!startsQuery || throwsOnError) return
131
+
132
+ const parent = node.parent
133
+ if (parent.type === 'ExpressionStatement') {
134
+ context.report({ node, messageId: 'discardedResult' })
135
+ return
136
+ }
137
+ if (parent.type === 'VariableDeclarator' && parent.id.type === 'ObjectPattern') {
138
+ if (!patternReadsError(parent.id)) {
139
+ context.report({ node, messageId: 'unreadError' })
140
+ }
141
+ return
142
+ }
143
+ if (
144
+ parent.type === 'MemberExpression' &&
145
+ parent.object === node &&
146
+ parent.property.type === 'Identifier' &&
147
+ parent.property.name === 'data'
148
+ ) {
149
+ context.report({ node, messageId: 'unreadError' })
150
+ }
151
+ },
152
+ }
153
+ },
154
+ }
155
+
156
+ export const hstackPlugin = {
157
+ meta: { name: 'hstack', version: '1.0.0' },
158
+ rules: {
159
+ 'supabase-unread-error': supabaseUnreadErrorRule,
160
+ },
161
+ }
162
+
163
+ /**
164
+ * @param {object} [options]
165
+ * @param {string[]} [options.files] Files the rules apply to.
166
+ * @param {string[]} [options.testFiles] Files where size rules are relaxed.
167
+ * @param {string[]} [options.scriptFiles] Operator CLIs, where stdout is the interface.
168
+ * @param {boolean} [options.typescript] Emit rules that need `@typescript-eslint` registered. Default true.
169
+ * @param {boolean} [options.imports] Emit rules that need `eslint-plugin-import` registered. Default false.
170
+ * @param {boolean} [options.supabase] Emit the unread-`error` rule. Default false.
171
+ * @param {Partial<typeof DEFAULT_THRESHOLDS>} [options.thresholds]
172
+ * @param {object} [options.modules] Where the one sanctioned path for each concern lives; used in messages.
173
+ * @param {string} [options.modules.logger] e.g. 'lib/observability/logger'
174
+ * @param {string} [options.modules.config] e.g. 'lib/env'
175
+ * @param {string} [options.modules.dbClient] e.g. 'lib/supabase'
176
+ * @returns {object[]} Flat-config objects to spread into `defineConfig([...])`.
177
+ */
178
+ export function cleanCode(options = {}) {
179
+ const files = options.files ?? DEFAULT_FILES
180
+ const testFiles = options.testFiles ?? DEFAULT_TEST_FILES
181
+ const scriptFiles = options.scriptFiles ?? DEFAULT_SCRIPT_FILES
182
+ const typescript = options.typescript ?? true
183
+ const imports = options.imports ?? false
184
+ const supabase = options.supabase ?? false
185
+ const t = { ...DEFAULT_THRESHOLDS, ...(options.thresholds ?? {}) }
186
+ const modules = options.modules ?? {}
187
+
188
+ const loggerHint = modules.logger ? ` Use ${modules.logger}.` : ' Use the repo logger.'
189
+ const configHint = modules.config
190
+ ? ` Read it through ${modules.config}, which validates every variable once at boot.`
191
+ : ' Read it through the one config module that validates every variable at boot.'
192
+ const dbClientHint = modules.dbClient
193
+ ? ` Get a client from ${modules.dbClient}; a client built elsewhere can bypass RLS.`
194
+ : ' Get a client from the one sanctioned module; a client built elsewhere can bypass RLS.'
195
+
196
+ const sizeAndShape = {
197
+ 'max-lines-per-function': [
198
+ 'error',
199
+ { max: t.functionLines, skipBlankLines: true, skipComments: true, IIFEs: true },
200
+ ],
201
+ 'max-lines': ['error', { max: t.fileLines, skipBlankLines: true, skipComments: true }],
202
+ complexity: ['error', t.complexity],
203
+ 'max-depth': ['error', t.depth],
204
+ 'max-params': ['error', t.params],
205
+ 'max-nested-callbacks': ['error', t.callbacks],
206
+ }
207
+
208
+ const errorsAreHandled = {
209
+ 'no-empty': ['error', { allowEmptyCatch: false }],
210
+ 'prefer-promise-reject-errors': 'error',
211
+ 'no-throw-literal': 'error',
212
+ }
213
+
214
+ // `no-console` carries no custom message; when the consumer names a logger,
215
+ // the restricted-global form says where to go instead.
216
+ const consoleRules = modules.logger
217
+ ? {
218
+ 'no-console': 'off',
219
+ 'no-restricted-globals': [
220
+ 'error',
221
+ { name: 'console', message: `Do not log through console.${loggerHint}` },
222
+ ],
223
+ }
224
+ : { 'no-console': ['error', { allow: [] }] }
225
+ const consoleOff = { 'no-console': 'off', 'no-restricted-globals': 'off' }
226
+
227
+ const onePathPerConcern = {
228
+ ...consoleRules,
229
+ 'no-restricted-syntax': [
230
+ 'error',
231
+ {
232
+ selector: "MemberExpression[object.name='process'][property.name='env']",
233
+ message: `Do not read process.env here.${configHint}`,
234
+ },
235
+ ],
236
+ 'no-restricted-imports': [
237
+ 'error',
238
+ {
239
+ paths: [
240
+ {
241
+ name: '@supabase/supabase-js',
242
+ message: `Do not import @supabase/supabase-js directly.${dbClientHint}`,
243
+ },
244
+ ],
245
+ },
246
+ ],
247
+ }
248
+
249
+ const deadCode = {
250
+ // The core rule is replaced by its type-aware twin when TypeScript is on.
251
+ 'no-unused-vars': typescript ? 'off' : 'error',
252
+ 'no-duplicate-imports': 'error',
253
+ 'no-unreachable': 'error',
254
+ 'no-useless-return': 'error',
255
+ }
256
+
257
+ const typescriptRules = typescript
258
+ ? {
259
+ '@typescript-eslint/no-unused-vars': [
260
+ 'error',
261
+ { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
262
+ ],
263
+ '@typescript-eslint/no-explicit-any': 'error',
264
+ '@typescript-eslint/no-non-null-assertion': 'error',
265
+ '@typescript-eslint/consistent-type-imports': [
266
+ 'error',
267
+ { prefer: 'type-imports', fixStyle: 'inline-type-imports' },
268
+ ],
269
+ // Type-aware: needs `parserOptions.projectService` (or `project`) set by the consumer.
270
+ '@typescript-eslint/no-floating-promises': 'error',
271
+ '@typescript-eslint/only-throw-error': 'error',
272
+ }
273
+ : {}
274
+
275
+ const importRules = imports ? { 'import/no-cycle': ['error', { maxDepth: Infinity }] } : {}
276
+
277
+ const supabaseRules = supabase ? { 'hstack/supabase-unread-error': 'error' } : {}
278
+
279
+ const configs = [
280
+ {
281
+ name: 'hstack/clean-code',
282
+ files,
283
+ ...(supabase ? { plugins: { hstack: hstackPlugin } } : {}),
284
+ rules: {
285
+ ...sizeAndShape,
286
+ ...errorsAreHandled,
287
+ ...onePathPerConcern,
288
+ ...deadCode,
289
+ ...typescriptRules,
290
+ ...importRules,
291
+ ...supabaseRules,
292
+ },
293
+ },
294
+ {
295
+ // A long test is a normal test, and a test file reads the environment to
296
+ // decide what it can run against.
297
+ name: 'hstack/clean-code/tests',
298
+ files: testFiles,
299
+ rules: {
300
+ 'max-lines-per-function': 'off',
301
+ 'max-lines': 'off',
302
+ 'max-nested-callbacks': 'off',
303
+ 'no-restricted-syntax': 'off',
304
+ ...(typescript ? { '@typescript-eslint/no-non-null-assertion': 'off' } : {}),
305
+ },
306
+ },
307
+ {
308
+ // Operator-run CLIs: stdout is the interface, and the process reads its
309
+ // own environment.
310
+ name: 'hstack/clean-code/scripts',
311
+ files: scriptFiles,
312
+ rules: {
313
+ ...consoleOff,
314
+ 'no-restricted-syntax': 'off',
315
+ },
316
+ },
317
+ ]
318
+
319
+ return configs
320
+ }
321
+
322
+ export default cleanCode
@@ -1,190 +1,96 @@
1
- ---
2
- id: infrastructure
3
- type: infrastructure
4
- status: drafted # drafted | current | needs-refresh | archived
5
- owner: <git-handle>
6
- last-quarterly-review: <YYYY-MM-DD>
7
- created: <YYYY-MM-DD>
8
- updated: <YYYY-MM-DD>
9
- schema-version: 1
10
- ---
1
+ _Where things run, why the couplings are what they are, and the traps. Operational truth, not policy — read it before touching env, deploy or dependencies (kernel § Context docs), and update it in the PR that makes it wrong. An honest "we don't have this yet" is the right answer whenever it is the true one._
11
2
 
12
- _Operational truth about how the consuming repo's product runs. Truth-gathering, not policy — `threat-model.md` and `hardening-checklist.md` carry the policy. This file is what `security-reviewer`, `data-specialist`, and any future `infra-specialist` read to ground change-time review in the system's actual shape. Length norm: 600–1500 lines once filled; shorter is honest for pre-prod, longer is a smell. Every H2 below must be present (validator rule INF-01); the Unknowns section must be present even when empty (INF-02); the Blast-Radius Matrix must list at least one row when status moves to `current` (INF-03); no MCP server may be wired with always-on write capability against prod (INF-04); LLM-driven sessions with a write-capable MCP active must not read user-generated content in the same session (INF-05)._
3
+ ## Where things run
13
4
 
14
- ## Hosting & Compute
5
+ _One row per distinct runtime — app, edge functions, workers, cron, queue consumers. Names copied from the provider console, not paraphrased._
15
6
 
16
- _Where each runtime lives. Provider, region(s), service tier/plan, instance class or function memory. Names must match the cloud console exactly — copy them, do not paraphrase. One row per distinct runtime (app, edge functions, workers, cron, queue consumers)._
17
-
18
- | Runtime | Provider | Region(s) | Tier / Plan | Notes |
7
+ | Runtime | Provider | Region(s) | Plan / tier | Notes |
19
8
  |---|---|---|---|---|
20
9
  | | | | | |
21
10
 
22
- ## Networking
23
-
24
- _DNS provider and zone, custom domains, TLS/SSL provisioning, CDN / edge cache, load balancing, ingress rules. Note which records are managed in code (IaC) vs the provider console._
25
-
26
- - **DNS provider.**
27
- - **Domains in use.**
28
- - **TLS provisioning.**
29
- - **CDN / edge.**
30
- - **Load balancing.**
31
- - **Egress controls.**
11
+ - **Domains, DNS, TLS, CDN.**
12
+ - **Declared in code vs clicked in a console.** List the clicked resources explicitly — each one is a load-bearing piece of tribal knowledge.
32
13
 
33
- ## Data Layer
14
+ ## Data & storage
34
15
 
35
- _Database hosting, connection pooling, backup cadence, point-in-time recovery window, read replicas, retention policy. Defers schema and RLS to `data-architecture.md`; this section is operational (where and how, not what)._
16
+ _Operational only. Schema, tenancy and RLS live in `data-architecture.md`._
36
17
 
37
- - **Primary database.**
38
- - **Connection pooling.**
39
- - **Backups.**
40
- - **Point-in-time recovery.**
18
+ - **Primary database** — host, plan, connection pooling.
19
+ - **Backups** — cadence, retention, point-in-time-recovery window.
20
+ - **Last restore drill** — date, and what it proved. An untested backup is a wish.
41
21
  - **Replicas / failover.**
42
- - **Retention.**
43
- - **Vector / search infrastructure.**
44
-
45
- ## Storage
46
-
47
- _Object storage, file uploads, presigned-URL flows, lifecycle and retention. One row per bucket / container._
48
-
49
- | Bucket | Provider | Purpose | Public? | Lifecycle | Notes |
50
- |---|---|---|---|---|---|
51
- | | | | | | |
52
-
53
- ## Secrets & Configuration
54
-
55
- _Where secrets live, how they are injected at runtime, rotation cadence, who has access. **The runbook for rotation lives in `incident-runbook.md`; this section names the system, not the steps.**_
56
-
57
- - **Secret store.**
58
- - **Injection mechanism.**
59
- - **Rotation cadence.**
60
- - **Access control.**
61
- - **Audit trail.**
62
-
63
- ## Environment Separation
64
-
65
- _What dev, staging (if present), and production look like, and what differs between them. Data-promotion rules (does prod data ever flow downstream? if so, with what redaction?). Connection-string isolation. **Test-against-prod is a kernel-level forbidden tool; flag any violation here as tech-debt.**_
66
-
67
- - **Dev.**
68
- - **Staging.**
69
- - **Production.**
70
- - **Data-promotion rules.**
71
- - **Cross-environment guardrails.**
72
-
73
- ## IaC Inventory
74
-
75
- _What infrastructure is declared in code vs configured by click in a console. Path to IaC files. Tool (Terraform, Pulumi, CDK, OpenTofu, Supabase migrations, GitHub Actions YAML, Dockerfile). Who can apply. **Click-configured infra is not forbidden, but every clicked resource is a load-bearing tribal-knowledge item; list each one explicitly so it can be promoted to IaC over time.**_
76
-
77
- | Resource | Source-of-truth | Path / location | Applier(s) | Notes |
78
- |---|---|---|---|---|
79
- | | | | | |
80
-
81
- ## Deploy Pipeline
82
-
83
- _How code reaches each environment. References `ci-cd.md` for the build pipeline; this section captures the deploy half — promotion mechanics, rollout strategy (instant cutover, canary, blue/green), rollback procedure, who can trigger a deploy._
84
-
85
- - **Build → deploy handoff.**
86
- - **Promotion path.**
87
- - **Rollout strategy.**
88
- - **Rollback procedure.**
89
- - **Deploy authorization.**
90
-
91
- ## Observability
22
+ - **Vector or search infrastructure.**
23
+ - **Buckets** one line each: purpose, public or not, lifecycle.
92
24
 
93
- _Logs, metrics, traces, error tracking, uptime monitoring. Where each lives, retention window, who reads them. Alert routing — which alerts wake whom, on which channel. **An observability gap is a production-readiness gap; an honest "we don't have this yet" entry is the right answer when true.**_
25
+ ## Environments
94
26
 
95
- - **Logs.**
96
- - **Metrics.**
97
- - **Traces.**
98
- - **Error tracking.**
99
- - **Uptime monitoring.**
100
- - **Alert routing.**
101
-
102
- ## Cost & Capacity
103
-
104
- _Current monthly spend per service (approximate is fine). Budget alerts. Scaling triggers and ceilings. Rate-limiting posture (per-route, per-tenant). **Cost is a security and reliability concern, not just a finance concern: an unbounded scale ceiling is a billing-DoS vector.**_
27
+ _What dev, preview and production are, and what actually differs between them._
105
28
 
106
- | Service | Monthly spend (approx) | Budget alert at | Scale ceiling | Notes |
29
+ | Environment | App URL | Database it points at | Seed data | Notes |
107
30
  |---|---|---|---|---|
108
31
  | | | | | |
109
32
 
110
- - **Rate limits.**
111
- - **Per-tenant quotas.**
33
+ - **Source of truth for environment variables**, and how they are scoped per environment.
34
+ - **Does production data ever flow downstream?** If so, with what redaction.
35
+ - **Cross-environment guardrails.** Testing against production is a kernel-forbidden path; a violation found here is a tech-debt file, not a footnote.
112
36
 
113
- ## Disaster Recovery
114
-
115
- _Recovery Point Objective (RPO) and Recovery Time Objective (RTO) targets per critical resource. Backup restore procedure (high level — full steps live in `incident-runbook.md`). Drill cadence — when was the last successful restore test, and when is the next scheduled. **An untested backup is a wish, not a recovery plan.**_
116
-
117
- | Resource | RPO target | RTO target | Last drill | Next drill |
118
- |---|---|---|---|---|
119
- | | | | | |
37
+ ## Secrets
120
38
 
121
- ## Blast-Radius Matrix
39
+ - **Where they live.** The one store that is authoritative.
40
+ - **How they reach the runtime.**
41
+ - **Who has access**, and how that is reviewed.
42
+ - **Rotation** — cadence, and where the steps are written down.
122
43
 
123
- _Per critical resource: what depends on it, what dies if it dies, and who is notified. This is the table that `security-reviewer` reads when scoring an infra-surface change; it must list at least one row when status moves to `current` (INF-03)._
44
+ ## Deploy Pipeline
124
45
 
125
- | Resource | Depends on | What dies if this dies | Notification path | Mitigation |
126
- |---|---|---|---|---|
127
- | | | | | |
46
+ _How code reaches production, and the exact commands `/hstack-promote` will look for here. A command left blank halts the promotion — which is the correct outcome, because guessing a production command is how the wrong project gets migrated._
128
47
 
129
- ## Access & Change Control
48
+ | What `/hstack-promote` needs | Command |
49
+ |---|---|
50
+ | Apply a pending migration to production | |
51
+ | List deployments and their state | |
52
+ | Smoke-test a specific deployment URL | |
53
+ | Read production logs | |
54
+ | Promote a deployment | |
55
+ | Roll back to the previous deployment | |
130
56
 
131
- _Who has production console access per provider. MFA enforcement. Audit log location and retention. Deploy authorization — who can push to prod, who can apply IaC, who can rotate secrets. **The principle of least privilege applies here; list humans by name and access scope, not role abstractions.**_
57
+ - **Promotion path.** How a merge becomes an unpromoted production build, and what promotes it. Auto-assignment of the production domain must be off for the staged flow to exist at all.
58
+ - **Who may promote.**
59
+ - **Rollback.** What re-promoting the previous deployment does and does not undo — migrations are additive, so they stay.
132
60
 
133
- | Human | Provider | Scope | MFA enforced | Last access review |
134
- |---|---|---|---|---|
135
- | | | | | |
61
+ ## Observability
136
62
 
137
- - **Audit log location.**
138
- - **Audit log retention.**
139
- - **Access review cadence.**
63
+ _Where `/hstack-promote` looks in its post-promotion window, and where a daily glance happens._
140
64
 
141
- ## MCP Access Policy
65
+ - **Logs** where, retention.
66
+ - **Error tracking** — tool, project, who watches it.
67
+ - **Uptime monitoring.**
68
+ - **Alert routing** — which alert reaches whom, on which channel. "Nobody is paged" is an answer; write it down rather than implying one.
142
69
 
143
- _Which MCP servers are wired, where they point, what they can do. MCP access is a security boundary equivalent to the access token it carries — an LLM-driven session with tool access to a project-scoped token has the project's full blast radius. The kernel already forbids `service_role` Supabase keys and `supabase db push` / `db reset` against remote environments; MCP write access against prod is the analogous capability and follows the same rule. One row per MCP server per project it points at._
70
+ ## Couplings & gotchas
144
71
 
145
- | MCP server | Wired at | Points at | Access mode | Token storage | Rotation cadence | Notes |
146
- |---|---|---|---|---|---|---|
147
- | | | | | | | |
72
+ _The section that earns this file. Everything a newcomer human or agent would otherwise learn by breaking production: a region that must match another region, a version pinned because the next one broke us, a service that must be deployed before another, a quota that bites at a specific hour._
148
73
 
149
- **Rule (INF-04).** No MCP server may be wired with write capability against the production project. Read-only mode (the server's `--read-only` flag or equivalent) is the floor for any MCP that points at prod. When a write-capable MCP must exist against prod for an operational reason (one-off migration applied through the MCP, e.g.), the row above carries a `--write-justified-by: <change-spec-id or ADR id>` note and the MCP is disabled by default — enabled only inside the named change window, then immediately disabled. Always-on write-capable prod MCPs are forbidden.
74
+ -
150
75
 
151
- **Rule (INF-05).** Any LLM-driven session that has a write-capable MCP tool active must not, in the same session, read user-generated content from a tenant-scoped table. This is the prompt-injection mitigation: prevents stored content (customer support rows, webhook payloads, user-submitted fields) from steering the LLM into destructive tool calls. List each session pattern below — subagent name, Skill, or ad-hoc and which side of this boundary it sits on.
76
+ _External services this depends on at runtime. Criticality: `hard` = the product is down, `soft` = degraded, `optional` = no user impact._
152
77
 
153
- | Session pattern | Write-capable MCP tools active? | Reads tenant-scoped content? | Compliant? |
78
+ | Provider | Purpose | Criticality | What happens when it is down |
154
79
  |---|---|---|---|
155
80
  | | | | |
156
81
 
157
- **Per-MCP detail.**
158
-
159
- - **Supabase MCP.** Dev project, staging project, production project — each on its own row. "Not wired" is an acceptable and often preferred value, especially for production.
160
- - **Other MCPs** (Notion, GitHub, Linear, Figma, Slack, etc.). Each with its own access scope, token location, rotation. Notion and Slack MCPs in particular often surface external user-generated content into the session — flag them explicitly under INF-05.
161
-
162
- ## Compliance & Data Residency
163
-
164
- _Regions where customer data lives at rest. GDPR-relevant flows (subject-access, deletion, export). Encryption at rest and in transit per data class. SOC 2 readiness gaps (this is honest enumeration, not a claim of posture). **v1 hstack does not by itself deliver SOC 2 or GDPR posture; the kernel says so explicitly. This section catalogs the gap, it does not close it.**_
82
+ ## MCP access
165
83
 
166
- - **Data residency.**
167
- - **GDPR flows.**
168
- - **Encryption at rest.**
169
- - **Encryption in transit.**
170
- - **Known compliance gaps.**
84
+ _Which MCP servers are wired, where they point, what they can do. An MCP is a security boundary equivalent to the token it carries: a session with a project-scoped token has that project's full blast radius. The rules — no write-capable MCP against production outside `/hstack-promote`, and no write-capable MCP active in a session that reads user-generated tenant-scoped content — belong to the kernel § Stop conditions. This table is the inventory those rules are applied to._
171
85
 
172
- ## Third-party Dependencies
173
-
174
- _External SaaS the system depends on at runtime. One row per provider. Criticality reflects what happens if the provider is unavailable: `hard` = product is down, `soft` = degraded, `optional` = no user impact._
175
-
176
- | Provider | Purpose | Criticality | Contractual SLO | Failure-mode behavior |
86
+ | MCP server | Points at | Access mode | Token storage | Notes |
177
87
  |---|---|---|---|---|
178
88
  | | | | | |
179
89
 
180
- ## Known Gaps
181
-
182
- _Open tech-debt items pointing to infrastructure shortcomings. Each entry is a link to a `hstack/tech-debt/TD-NNNN-<slug>.md` file. This section is the bridge between the operational truth captured above and the workflow's tech-debt machinery — every honest gap surfaced during the interview should land as a TD via `/hstack:tech-debt-new --origin <change-id>`._
183
-
184
- -
90
+ _"Not wired" is an acceptable value, and often the preferred one for production._
185
91
 
186
- ## Unknowns
92
+ ## Known gaps
187
93
 
188
- _Challenge prompt: what infrastructure dependency does the team not yet have a documented mitigation for? Name the dependency, name the failure mode, name the gap. This section must be present even when empty, to make the absence explicit (validator rule INF-02)._
94
+ _What is missing or unknown, named rather than implied. A gap with a shape is a file under `hstack/tech-debt/`; link it here. A gap without one a dependency whose failure mode nobody has thought through is a line here until someone does._
189
95
 
190
96
  -
@@ -1,18 +1,12 @@
1
1
  ---
2
- id: roadmap
3
- type: roadmap
4
- status: drafted # drafted | current | needs-refresh | archived
5
- owner: <git-handle>
6
- source: local # local | rhizome — who owns the truth; when rhizome, local edits are refused and the sync is authoritative
7
- reviewed-on: <YYYY-MM-DD> # planner treats the roadmap as stale beyond 90 days
8
- created: <YYYY-MM-DD>
9
- updated: <YYYY-MM-DD>
10
- schema-version: 1
2
+ reviewed-on: <YYYY-MM-DD> # past 90 days, treat this roadmap as stale rather than current
11
3
  ---
12
4
 
5
+ _Advisory only, never a gate (kernel § Context docs). It informs one-way-door decisions and the Forecloses / Enables section of an ADR; it never decides whether a change ships._
6
+
13
7
  ## Now
14
8
 
15
- _What we are committed to shipping. During the MVP phase, Now IS the MVP scope. Each item: one product line, one architectural-implication line. Implications are proposed by app-architect / data-architect and confirmed by the engineer — vague fortune-telling ("might need to scale") is worse than absence._
9
+ _What we are committed to shipping. Each item: one product line, one architectural-implication line. Vague fortune-telling ("might need to scale") is worse than absence._
16
10
 
17
11
  - <item> — **architectural implication:** <one line>
18
12
 
@@ -30,12 +24,12 @@ _Plausible futures. These influence one-way-door decisions ONLY — never day-to
30
24
 
31
25
  ## Not on the path
32
26
 
33
- _Explicit renouncements, with the reason. Absorbs the old "Deferred" list._
27
+ _Explicit renouncements, with the reason._
34
28
 
35
29
  - <item> — <why not>
36
30
 
37
31
  ## Success criteria for the current horizon
38
32
 
39
- _Concrete observable indicators that Now is done and Next gets promoted. Bullets._
33
+ _Concrete observable indicators that Now is done and Next gets promoted._
40
34
 
41
35
  -
@@ -1,20 +1,14 @@
1
1
  ---
2
- id: <STORE>:<store-native-id> # one of NOTION:<id> | LINEAR:<key> | GH:<num> | REPO:<slug>
2
+ id: NOTION:<page-id>
3
3
  type: story
4
- status: drafted # drafted | ready | in-flight | shipped | archived
5
- owner: <git-handle>
6
- persona: <persona-id> # single persona id
7
- job-to-be-done: <one-sentence summary; mirrors section 1>
8
- success-metric: <how shipping is measured>
9
- linked-change-specs: [] # required non-empty when status: in-flight
10
- created: <YYYY-MM-DD>
11
- updated: <YYYY-MM-DD>
12
- schema-version: 1
4
+ persona: <persona-slug> # the persona this story is written from
5
+ job-to-be-done: <one sentence; mirrors section 1>
6
+ success-metric: <how we know shipping it worked>
13
7
  ---
14
8
 
15
9
  ## Who and Why
16
10
 
17
- _Persona id (pointer, not duplicated) and the job-to-be-done. 1–2 sentences._
11
+ _The persona (pointer, not duplicated) and the job to be done. 1–2 sentences._
18
12
 
19
13
  ## What Shipping Looks Like
20
14
 
@@ -22,11 +16,11 @@ _What the user sees, does, feels. One paragraph, 3–5 sentences._
22
16
 
23
17
  ## Success Metric
24
18
 
25
- _Pointer to the `success-metric` frontmatter field with a one-sentence explanation of how it is measured._
19
+ _The `success-metric` above, with one sentence on how it is measured._
26
20
 
27
21
  ## Edge Cases the User Cares About
28
22
 
29
- _Challenge prompt: what does the user notice if this ships but is slightly broken? 2–5 bullets._
23
+ _Challenge: what does the user notice if this ships but is slightly broken? 2–5 bullets._
30
24
 
31
25
  -
32
26