@rashidee/co2 1.2.5
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/dist/index.js +25449 -0
- package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
- package/drizzle/0001_wise_wolf_cub.sql +13 -0
- package/drizzle/0002_secret_spyke.sql +16 -0
- package/drizzle/0003_workspace_config_singleton.sql +16 -0
- package/drizzle/0004_terminal_claim_v120.sql +11 -0
- package/drizzle/0005_brainstorm_v124.sql +10 -0
- package/drizzle/0006_project_v140.sql +6 -0
- package/drizzle/0007_prd_wizard_v124.sql +14 -0
- package/drizzle/0008_workflow_artifact_v126.sql +24 -0
- package/drizzle/0009_project_import_v150.sql +7 -0
- package/drizzle/0010_quality_v151.sql +3 -0
- package/drizzle/meta/0000_snapshot.json +1335 -0
- package/drizzle/meta/0001_snapshot.json +1416 -0
- package/drizzle/meta/0002_snapshot.json +1510 -0
- package/drizzle/meta/_journal.json +83 -0
- package/package.json +41 -0
- package/plugin/.claude-plugin/marketplace.json +36 -0
- package/plugin/.claude-plugin/plugin.json +22 -0
- package/plugin/README.md +625 -0
- package/plugin/SKILLS.md +350 -0
- package/plugin/co2.svg +5 -0
- package/plugin/commands/brainstorm-loop.md +129 -0
- package/plugin/skills/conductor-defect/SKILL.md +898 -0
- package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
- package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
- package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
- package/plugin/skills/depgen-k8s/SKILL.md +468 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
- package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
- package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
- package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
- package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
- package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
- package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
- package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
- package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
- package/plugin/skills/modelgen-relational/SKILL.md +707 -0
- package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
- package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
- package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
- package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
- package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
- package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
- package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
- package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
- package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
- package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
- package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
- package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
- package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
- package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
- package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
- package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
- package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
- package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
- package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
- package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
- package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
- package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
- package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
- package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
- package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
- package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
- package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
- package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
- package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
- package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
- package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
- package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
- package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
- package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
- package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
- package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
- package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
- package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
- package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
- package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
- package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
- package/plugin/skills/testgen-functional/SKILL.md +1381 -0
- package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
- package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
- package/plugin/skills/util-projectinit/SKILL.md +307 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
- package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
- package/plugin/skills/util-projectsync/SKILL.md +501 -0
- package/plugin/skills/util-updprd/SKILL.md +324 -0
- package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
- package/plugin/skills/util-ustagger/SKILL.md +154 -0
- package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
- package/static/assets/arc-DRKw7sjs.js +1 -0
- package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
- package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
- package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
- package/static/assets/channel-C2nVbuwv.js +1 -0
- package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
- package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
- package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
- package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
- package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
- package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
- package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
- package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
- package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
- package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
- package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
- package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
- package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
- package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
- package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
- package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
- package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
- package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
- package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
- package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
- package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
- package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
- package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
- package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
- package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
- package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
- package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
- package/static/assets/graph-C9eacEi8.js +1 -0
- package/static/assets/index-LZSQkLE2.css +41 -0
- package/static/assets/index-y7XDSgMG.js +495 -0
- package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
- package/static/assets/init-Gi6I4Gst.js +1 -0
- package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
- package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
- package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
- package/static/assets/katex-C5jXJg4s.js +257 -0
- package/static/assets/layout-DEXfKzaS.js +1 -0
- package/static/assets/linear-CLO--ryk.js +1 -0
- package/static/assets/map-Czzmt4hB.js +1 -0
- package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
- package/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
- package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
- package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
- package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
- package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
- package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
- package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
- package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
- package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
- package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
- package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
- package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
- package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
- package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
- package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
- package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
- package/static/index.html +24 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Server Patterns — specgen-node-cli-web
|
|
2
|
+
|
|
3
|
+
Canonical patterns for the data directory (root spec Section 5), database layer
|
|
4
|
+
(Section 6), and Hono server composition (Section 7).
|
|
5
|
+
|
|
6
|
+
## 1. Data directory resolution
|
|
7
|
+
|
|
8
|
+
Per-platform, XDG-compliant, keyed by the package name. Resolve manually (shown below) or
|
|
9
|
+
via `env-paths` — no heavier dependency:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { homedir } from 'node:os'
|
|
13
|
+
import { join } from 'node:path'
|
|
14
|
+
import { mkdirSync } from 'node:fs'
|
|
15
|
+
|
|
16
|
+
export function resolveDataDir(override?: string): string {
|
|
17
|
+
const dir = override ?? defaultDataDir()
|
|
18
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 })
|
|
19
|
+
return dir
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function defaultDataDir(): string {
|
|
23
|
+
const app = '{{PACKAGE_NAME}}'
|
|
24
|
+
switch (process.platform) {
|
|
25
|
+
case 'darwin':
|
|
26
|
+
return join(homedir(), 'Library', 'Application Support', app)
|
|
27
|
+
case 'win32':
|
|
28
|
+
return join(process.env.LOCALAPPDATA ?? join(homedir(), 'AppData', 'Local'), app)
|
|
29
|
+
default:
|
|
30
|
+
return join(process.env.XDG_DATA_HOME ?? join(homedir(), '.local', 'share'), app)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Contents: `app.db` (+ WAL/SHM files), `config.json`, `logs/` (optional),
|
|
36
|
+
[If FileStorage] `files/`. Rules:
|
|
37
|
+
|
|
38
|
+
- Created on first run with `0700` permissions.
|
|
39
|
+
- The package installation directory is read-only at runtime (global npm installs may not
|
|
40
|
+
be writable) — NEVER write anything next to the installed code.
|
|
41
|
+
|
|
42
|
+
## 2. config.json
|
|
43
|
+
|
|
44
|
+
Zod-validated on load; unknown keys rejected (`.strict()`). Absent file = all defaults.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
48
|
+
import { join } from 'node:path'
|
|
49
|
+
import { z } from 'zod'
|
|
50
|
+
|
|
51
|
+
export const configSchema = z
|
|
52
|
+
.object({
|
|
53
|
+
port: z.number().int().min(1).max(65535).optional(),
|
|
54
|
+
host: z.string().optional(),
|
|
55
|
+
// [If HttpIntegration] apiBaseUrl: z.string().url().optional(),
|
|
56
|
+
// ...one key per config-driven setting discovered in the PRD
|
|
57
|
+
})
|
|
58
|
+
.strict()
|
|
59
|
+
|
|
60
|
+
export type AppConfig = z.infer<typeof configSchema>
|
|
61
|
+
|
|
62
|
+
export function loadConfig(dataDir: string): AppConfig {
|
|
63
|
+
const file = join(dataDir, 'config.json')
|
|
64
|
+
if (!existsSync(file)) return {}
|
|
65
|
+
return configSchema.parse(JSON.parse(readFileSync(file, 'utf8')))
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 3. Database connection factory
|
|
70
|
+
|
|
71
|
+
No module-level singletons bound to `process.env` — the factory takes the resolved
|
|
72
|
+
data-dir path (or `:memory:`) so tests can inject in-memory databases. The three pragmas
|
|
73
|
+
are mandatory:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import Database from 'better-sqlite3'
|
|
77
|
+
import { drizzle } from 'drizzle-orm/better-sqlite3'
|
|
78
|
+
import { migrate } from 'drizzle-orm/better-sqlite3/migrator'
|
|
79
|
+
import { join, dirname } from 'node:path'
|
|
80
|
+
import { fileURLToPath } from 'node:url'
|
|
81
|
+
import * as schema from './schema.js'
|
|
82
|
+
|
|
83
|
+
export type Db = ReturnType<typeof openDatabase>
|
|
84
|
+
|
|
85
|
+
export function openDatabase(dataDirOrMemory: string) {
|
|
86
|
+
const path =
|
|
87
|
+
dataDirOrMemory === ':memory:' ? ':memory:' : join(dataDirOrMemory, 'app.db')
|
|
88
|
+
const sqlite = new Database(path)
|
|
89
|
+
sqlite.pragma('journal_mode = WAL')
|
|
90
|
+
sqlite.pragma('foreign_keys = ON')
|
|
91
|
+
sqlite.pragma('busy_timeout = 5000')
|
|
92
|
+
return drizzle(sqlite, { schema })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function runMigrations(db: Db): void {
|
|
96
|
+
// In the published package, drizzle/ sits next to dist/ — resolve relative to the
|
|
97
|
+
// bundle, never relative to process.cwd().
|
|
98
|
+
const migrationsFolder = join(dirname(fileURLToPath(import.meta.url)), '..', 'drizzle')
|
|
99
|
+
migrate(db, { migrationsFolder })
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Migrations run automatically on every `start` before the server binds. Idempotent; no
|
|
104
|
+
separate migrate command for end users. Migration SQL files are generated by
|
|
105
|
+
`drizzle-kit generate`, committed in `server/drizzle/`, and shipped in the package —
|
|
106
|
+
never hand-edited after commit.
|
|
107
|
+
|
|
108
|
+
## 4. Hono app factory
|
|
109
|
+
|
|
110
|
+
One Hono app serves everything. Middleware order is fixed:
|
|
111
|
+
**request-id → logger → session resolution → route guards → routes.**
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { Hono } from 'hono'
|
|
115
|
+
import { requestId } from 'hono/request-id'
|
|
116
|
+
import { secureHeaders } from 'hono/secure-headers'
|
|
117
|
+
import { serveStatic } from '@hono/node-server/serve-static'
|
|
118
|
+
import { join, dirname } from 'node:path'
|
|
119
|
+
import { fileURLToPath } from 'node:url'
|
|
120
|
+
import type { Db } from './db/index.js'
|
|
121
|
+
import { requestLogger } from './lib/logger.js'
|
|
122
|
+
import { sessionMiddleware, forcedChangeGuard } from './auth/middleware.js'
|
|
123
|
+
import { authRoutes } from './features/auth/routes.js'
|
|
124
|
+
import { userRoutes } from './features/users/routes.js'
|
|
125
|
+
// ...one import per module feature
|
|
126
|
+
|
|
127
|
+
export interface AppDeps {
|
|
128
|
+
db: Db
|
|
129
|
+
dataDir: string
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function createApp({ db, dataDir }: AppDeps) {
|
|
133
|
+
const app = new Hono<{ Variables: { db: Db; user: SessionUser | null } }>()
|
|
134
|
+
.use('*', requestId())
|
|
135
|
+
.use('*', requestLogger())
|
|
136
|
+
.use('*', secureHeaders())
|
|
137
|
+
.use('*', async (c, next) => {
|
|
138
|
+
c.set('db', db)
|
|
139
|
+
await next()
|
|
140
|
+
})
|
|
141
|
+
.use('/api/*', sessionMiddleware())
|
|
142
|
+
.use('/api/*', forcedChangeGuard())
|
|
143
|
+
|
|
144
|
+
const api = app
|
|
145
|
+
.route('/api/auth', authRoutes)
|
|
146
|
+
.route('/api/users', userRoutes)
|
|
147
|
+
// .route('/api/{{module}}', {{module}}Routes)
|
|
148
|
+
.get('/api/health', (c) =>
|
|
149
|
+
c.json({ status: 'ok', version: APP_VERSION, uptimeSeconds: process.uptime() }),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const staticRoot = join(dirname(fileURLToPath(import.meta.url)), '..', 'static')
|
|
153
|
+
app.use('*', serveStatic({ root: staticRoot }))
|
|
154
|
+
app.get('*', serveStatic({ root: staticRoot, rewriteRequestPath: () => '/index.html' }))
|
|
155
|
+
|
|
156
|
+
return api
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export type AppType = ReturnType<typeof createApp>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Rules:
|
|
163
|
+
- `/api/*` routes are mounted before `serveStatic`; the SPA fallback rewrites unknown
|
|
164
|
+
non-API GETs to `index.html` for client-side routing.
|
|
165
|
+
- Security headers on ALL responses via `hono/secure-headers`.
|
|
166
|
+
- **CORS is NOT enabled** — the UI is same-origin by construction. Never add permissive
|
|
167
|
+
CORS.
|
|
168
|
+
|
|
169
|
+
## 5. Typed API contract (hono/client RPC)
|
|
170
|
+
|
|
171
|
+
Routes are defined with `@hono/zod-validator`; the route tree's type (`AppType`) is
|
|
172
|
+
exported so `web/` consumes a fully typed client. The API contract is compiler-enforced
|
|
173
|
+
end to end — no OpenAPI codegen step.
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
// packages/web/src/lib/api.ts
|
|
177
|
+
import { hc } from 'hono/client'
|
|
178
|
+
import type { AppType } from '@{{PACKAGE_NAME}}/server/app'
|
|
179
|
+
|
|
180
|
+
export const api = hc<AppType>('/', {
|
|
181
|
+
fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
182
|
+
const res = await fetch(input, { ...init, credentials: 'same-origin' })
|
|
183
|
+
if (res.status === 403) {
|
|
184
|
+
const body = await res.clone().json().catch(() => null)
|
|
185
|
+
if (body?.code === 'PASSWORD_CHANGE_REQUIRED') {
|
|
186
|
+
window.location.assign('/force-change-password')
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return res
|
|
190
|
+
},
|
|
191
|
+
})
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Route definition pattern (per feature):
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { Hono } from 'hono'
|
|
198
|
+
import { zValidator } from '@hono/zod-validator'
|
|
199
|
+
import { createItemSchema, listQuerySchema } from '@{{PACKAGE_NAME}}/core/schemas/{{domain}}'
|
|
200
|
+
import { requireRole } from '../../auth/middleware.js'
|
|
201
|
+
import * as service from './service.js'
|
|
202
|
+
|
|
203
|
+
export const {{module}}Routes = new Hono<AppEnv>()
|
|
204
|
+
.get('/', zValidator('query', listQuerySchema), (c) =>
|
|
205
|
+
c.json(service.list(c.var.db, c.req.valid('query'))),
|
|
206
|
+
)
|
|
207
|
+
.post('/', requireRole('admin'), zValidator('json', createItemSchema), (c) =>
|
|
208
|
+
c.json(service.create(c.var.db, c.req.valid('json')), 201),
|
|
209
|
+
)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## 6. Structured logger
|
|
213
|
+
|
|
214
|
+
No logging framework — a small JSON-lines logger in `server/src/lib/logger.ts`,
|
|
215
|
+
picocolors-highlighted only when stdout is a TTY. The one-time start status block is the
|
|
216
|
+
sole non-structured output; everything after it goes through the logger.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import pc from 'picocolors'
|
|
220
|
+
|
|
221
|
+
type Level = 'info' | 'warn' | 'error'
|
|
222
|
+
|
|
223
|
+
export function log(level: Level, message: string, fields: Record<string, unknown> = {}): void {
|
|
224
|
+
const entry = { time: new Date().toISOString(), level, message, ...fields }
|
|
225
|
+
const line = JSON.stringify(entry)
|
|
226
|
+
if (!process.stdout.isTTY) {
|
|
227
|
+
process.stdout.write(`${line}\n`)
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
const color = level === 'error' ? pc.red : level === 'warn' ? pc.yellow : pc.dim
|
|
231
|
+
process.stdout.write(`${color(line)}\n`)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function requestLogger() {
|
|
235
|
+
return async (c: HonoContext, next: () => Promise<void>) => {
|
|
236
|
+
const started = performance.now()
|
|
237
|
+
await next()
|
|
238
|
+
log('info', 'request', {
|
|
239
|
+
requestId: c.var.requestId,
|
|
240
|
+
method: c.req.method,
|
|
241
|
+
path: c.req.path,
|
|
242
|
+
status: c.res.status,
|
|
243
|
+
durationMs: Math.round(performance.now() - started),
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# Specification Template — Self-Hosted Node.js CLI Web Application
|
|
2
|
+
|
|
3
|
+
This is the authoritative template for the generated specification. The specification is
|
|
4
|
+
split into **two types of files**:
|
|
5
|
+
|
|
6
|
+
1. **`SPECIFICATION.md`** (root) — Table of Contents, monorepo + shared infrastructure,
|
|
7
|
+
CLI contract, data directory, database, server composition, bootstrap, auth baseline,
|
|
8
|
+
SPA shell, testing, packaging. Generated once per application.
|
|
9
|
+
2. **`<module-name>/SPEC.md`** (per-module) — Self-contained module blueprint spanning
|
|
10
|
+
the server feature and the SPA feature. Generated once per business module from
|
|
11
|
+
PRD.md, PLUS the mandatory `user-management/SPEC.md`.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
specification/
|
|
15
|
+
├── SPECIFICATION.md
|
|
16
|
+
├── user-management/
|
|
17
|
+
│ └── SPEC.md
|
|
18
|
+
├── inventory/
|
|
19
|
+
│ └── SPEC.md
|
|
20
|
+
└── ...
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Placeholders use `{{VARIABLE}}` syntax and must be replaced with actual values.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Part A: Root SPECIFICATION.md
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Table of Contents
|
|
32
|
+
|
|
33
|
+
Generate a TOC with anchor links. Include a **Modules** section linking to each module's
|
|
34
|
+
`SPEC.md` (user-management first):
|
|
35
|
+
|
|
36
|
+
```markdown
|
|
37
|
+
## Table of Contents
|
|
38
|
+
|
|
39
|
+
### Shared Infrastructure
|
|
40
|
+
- [1. Project Overview](#1-project-overview)
|
|
41
|
+
- [2. Monorepo & Workspace Configuration](#2-monorepo--workspace-configuration)
|
|
42
|
+
- ...
|
|
43
|
+
|
|
44
|
+
### Modules
|
|
45
|
+
- [User Management (mandatory)](user-management/SPEC.md)
|
|
46
|
+
- [Inventory](inventory/SPEC.md)
|
|
47
|
+
- ...
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Section 1: Project Overview
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
# {{APPLICATION_NAME}} — Technical Specification
|
|
56
|
+
|
|
57
|
+
## 1. Project Overview
|
|
58
|
+
|
|
59
|
+
**Application Name**: {{APPLICATION_NAME}}
|
|
60
|
+
**Package / Binary**: {{PACKAGE_NAME}} (npm package name AND executable command)
|
|
61
|
+
**Install**: npm install -g {{PACKAGE_NAME}}
|
|
62
|
+
**Start**: {{PACKAGE_NAME}} start → http://<LAN-IP>:3000
|
|
63
|
+
**Node.js Version**: 22 LTS (engines.node ">=22")
|
|
64
|
+
**Description**: {{APP_DESCRIPTION}}
|
|
65
|
+
**Versions Covered**: v1.0.0 — v{{LATEST_VERSION}}
|
|
66
|
+
|
|
67
|
+
### Architecture Model
|
|
68
|
+
Single-process, local-first. One Node process serves the REST API (Hono) and the
|
|
69
|
+
embedded pre-built React SPA. All state lives in the per-user data directory. There is
|
|
70
|
+
no hosted backend, no database server, no Docker.
|
|
71
|
+
|
|
72
|
+
### Optional Components (Auto-Determined)
|
|
73
|
+
**DataGrid**: {{yes|no}}
|
|
74
|
+
**Charts**: {{yes|no}}
|
|
75
|
+
**DatePickers**: {{yes|no}}
|
|
76
|
+
**RichText**: {{yes|no}}
|
|
77
|
+
**FileStorage**: {{yes|no}}
|
|
78
|
+
**InProcessJobs**: {{yes|no}}
|
|
79
|
+
**HttpIntegration**: {{yes|no}}
|
|
80
|
+
**ExtraCommands**: {{list or none}}
|
|
81
|
+
|
|
82
|
+
### Technology Stack
|
|
83
|
+
(Render the core stack table from SKILL.md, plus selected optional versions)
|
|
84
|
+
|
|
85
|
+
### CLI Commands
|
|
86
|
+
| Command | Description | Version |
|
|
87
|
+
|---------------|------------------------------------------------|---------|
|
|
88
|
+
| start | Start the web application (foreground) | v1.0.0 |
|
|
89
|
+
| status | Show whether an instance is reachable | v1.0.0 |
|
|
90
|
+
| reset-admin | Regenerate the admin password offline | v1.0.0 |
|
|
91
|
+
| {{extra}} | {{description}} | {{ver}} |
|
|
92
|
+
|
|
93
|
+
### User Roles
|
|
94
|
+
| Role | Source (mockup folder) | Capabilities summary |
|
|
95
|
+
|-------|------------------------|----------------------|
|
|
96
|
+
| admin | mockup/admin/ | Full access + user management |
|
|
97
|
+
| user | mockup/user/ | {{...}} |
|
|
98
|
+
|
|
99
|
+
### Module Index
|
|
100
|
+
| Module | Spec File | Tables | SPA Routes |
|
|
101
|
+
|--------|-----------|--------|------------|
|
|
102
|
+
| User Management (mandatory) | [user-management/SPEC.md](user-management/SPEC.md) | users, sessions | /login, /force-change-password, /users, /settings |
|
|
103
|
+
| {{Module}} | [{{module}}/SPEC.md]({{module}}/SPEC.md) | {{tables}} | {{routes}} |
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Section 2: Monorepo & Workspace Configuration
|
|
109
|
+
|
|
110
|
+
Render the full monorepo tree (from SKILL.md, with actual feature/module names
|
|
111
|
+
substituted), then complete copy-pasteable config files:
|
|
112
|
+
|
|
113
|
+
- `pnpm-workspace.yaml` (`packages: ["packages/*"]`)
|
|
114
|
+
- Root `package.json` with the workspace scripts — exactly these:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"name": "{{PACKAGE_NAME}}-workspace",
|
|
119
|
+
"private": true,
|
|
120
|
+
"version": "{{VERSION}}",
|
|
121
|
+
"engines": { "node": ">=22" },
|
|
122
|
+
"packageManager": "pnpm@9.15.0",
|
|
123
|
+
"scripts": {
|
|
124
|
+
"dev": "pnpm --parallel --filter server --filter web dev",
|
|
125
|
+
"build": "pnpm --filter core --filter server --filter web --filter cli build",
|
|
126
|
+
"check": "tsc -b && biome check .",
|
|
127
|
+
"fix": "biome check --write .",
|
|
128
|
+
"test": "vitest run",
|
|
129
|
+
"test:e2e": "playwright test",
|
|
130
|
+
"smoke": "node packages/cli/dist/index.js --version && node scripts/smoke-start.mjs",
|
|
131
|
+
"verify": "pnpm check && pnpm test && pnpm build && pnpm smoke"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- `biome.json` (formatter + linter, recommended rules, 100-char line width)
|
|
137
|
+
- Root `tsconfig.json` (composite project references: core → server → web → cli) and each
|
|
138
|
+
package's `tsconfig.json` with `"strict": true`
|
|
139
|
+
- `packages/core/package.json`, `packages/server/package.json`,
|
|
140
|
+
`packages/web/package.json` (all `"private": true`)
|
|
141
|
+
|
|
142
|
+
State the structural rules: feature-folder colocation, no barrel files, <300 lines/file,
|
|
143
|
+
Zod at every boundary, `any` forbidden, types via `z.infer<>`.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Section 3: Published CLI Package & Build Pipeline
|
|
148
|
+
|
|
149
|
+
Read `references/cli-packaging-patterns.md`. Must include, copy-pasteable:
|
|
150
|
+
|
|
151
|
+
- `packages/cli/package.json` — `name: {{PACKAGE_NAME}}`, `bin`, `files: ["dist",
|
|
152
|
+
"static", "drizzle"]`, `engines`, `better-sqlite3` as the ONLY runtime dependency,
|
|
153
|
+
`prepublishOnly: "pnpm -w verify"`
|
|
154
|
+
- `packages/cli/tsup.config.ts` — entry `src/index.ts`, `format: ["esm"]`, shebang
|
|
155
|
+
preserved, `external: ["better-sqlite3"]`
|
|
156
|
+
- The three-step publish pipeline (bundle → vite build + copy to `static/` → copy
|
|
157
|
+
`drizzle/`) as npm scripts
|
|
158
|
+
- Install-weight budget note: tarball SHOULD stay under 5 MB excluding better-sqlite3;
|
|
159
|
+
new runtime dependencies require explicit human approval
|
|
160
|
+
- Local install check: `npm pack` → `npm i -g ./<tarball>` → smoke sequence
|
|
161
|
+
|
|
162
|
+
### Section 3a: Application Version Configuration
|
|
163
|
+
|
|
164
|
+
The `packages/cli/package.json` `version` field MUST be set to the version value derived
|
|
165
|
+
from the version argument provided during skill invocation (e.g., `1.0.3`). If multiple
|
|
166
|
+
versions were provided, use the highest one.
|
|
167
|
+
|
|
168
|
+
- `{{PACKAGE_NAME}} --version` prints it (commander `.version()` fed from the bundled
|
|
169
|
+
package.json)
|
|
170
|
+
- The `start` status block prints it
|
|
171
|
+
- The SPA footer renders `v{{VERSION}}` (served from a `/api/system/info` route — the SPA
|
|
172
|
+
must not hardcode it)
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Section 4: CLI Contract
|
|
177
|
+
|
|
178
|
+
Read `references/cli-packaging-patterns.md`. Must include:
|
|
179
|
+
|
|
180
|
+
- The full command surface with flags:
|
|
181
|
+
```
|
|
182
|
+
{{PACKAGE_NAME}} start [--port <n>] [--host <addr>] [--data-dir <path>]
|
|
183
|
+
{{PACKAGE_NAME}} status
|
|
184
|
+
{{PACKAGE_NAME}} reset-admin
|
|
185
|
+
{{PACKAGE_NAME}} --version | --help
|
|
186
|
+
```
|
|
187
|
+
- `src/index.ts` (shebang + commander program) — complete sample
|
|
188
|
+
- `commands/start.ts` — option resolution order (flag → `APP_PORT`/`APP_HOST`/
|
|
189
|
+
`APP_DATA_DIR` env → config.json → default 3000 / 0.0.0.0), port-in-use failure message
|
|
190
|
+
naming `--port`, LAN IP detection, the single status block, graceful shutdown closing
|
|
191
|
+
the SQLite handle — complete sample
|
|
192
|
+
- `commands/status.ts` and `commands/reset-admin.ts` — complete samples
|
|
193
|
+
- Explicit statements: foreground only, NO daemon/forking logic; handlers are thin
|
|
194
|
+
(parse → Zod → delegate to server/core)
|
|
195
|
+
- [If ExtraCommands] one subsection per extra command with traceability IDs
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Section 5: Data Directory & Configuration
|
|
200
|
+
|
|
201
|
+
Read `references/server-patterns.md`. Must include:
|
|
202
|
+
|
|
203
|
+
- Per-platform resolution table (Linux `~/.local/share/{{PACKAGE_NAME}}/`, macOS
|
|
204
|
+
`~/Library/Application Support/{{PACKAGE_NAME}}/`, Windows
|
|
205
|
+
`%LOCALAPPDATA%/{{PACKAGE_NAME}}/`) and the resolver sample (manual or `env-paths`)
|
|
206
|
+
- Contents: `app.db` (+ WAL/SHM), `config.json`, `logs/` (optional),
|
|
207
|
+
[If FileStorage] `files/`
|
|
208
|
+
- Creation on first run with `0700` permissions; install dir treated read-only
|
|
209
|
+
- `config.json` Zod schema (unknown keys rejected) with every key, type, and default —
|
|
210
|
+
complete sample
|
|
211
|
+
- Auto-migration on every start before the server binds, against the shipped `drizzle/`
|
|
212
|
+
folder
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Section 6: Database Layer
|
|
217
|
+
|
|
218
|
+
Must include, with complete samples:
|
|
219
|
+
|
|
220
|
+
- `server/src/db/schema.ts` — Drizzle definitions for `users` + `sessions` (from
|
|
221
|
+
`references/auth-patterns.md`) PLUS every module table from the model files,
|
|
222
|
+
field-for-field
|
|
223
|
+
- `server/src/db/index.ts` — connection factory taking the data-dir path (or `:memory:`),
|
|
224
|
+
mandatory pragmas: WAL, `foreign_keys = ON`, `busy_timeout = 5000`; no module-level
|
|
225
|
+
singletons
|
|
226
|
+
- `drizzle.config.ts` and the `db:generate` workflow; migrations committed in
|
|
227
|
+
`server/drizzle/` and shipped in the published package
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Section 7: Server Composition
|
|
232
|
+
|
|
233
|
+
Read `references/server-patterns.md`. Must include:
|
|
234
|
+
|
|
235
|
+
- `server/src/app.ts` app factory — complete sample showing middleware order
|
|
236
|
+
(request-id → logger → session resolution → route guards → routes), `/api/*` route
|
|
237
|
+
mounting, `secure-headers`, `serveStatic` + SPA fallback
|
|
238
|
+
- The exported `AppType` and the `hono/client` consumption pattern
|
|
239
|
+
- Health endpoint (`GET /api/health`) used by `status` and the smoke test
|
|
240
|
+
- Statement: CORS is NOT enabled; never add it
|
|
241
|
+
- `server/src/lib/logger.ts` — structured JSON-line logger, picocolors-highlighted when
|
|
242
|
+
TTY — complete sample
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Section 8: First-Run Bootstrap
|
|
247
|
+
|
|
248
|
+
Read `references/auth-patterns.md`. Must include `server/src/db/bootstrap.ts` complete
|
|
249
|
+
sample: users-table-empty check → create `admin` (role `admin`,
|
|
250
|
+
`must_change_password = true`) with a cryptographically random 16-character password
|
|
251
|
+
(base58/alphanumeric, no ambiguous characters) → return credentials for one-time printing
|
|
252
|
+
inside the start status block. Plaintext never written to file or log; no-op once any
|
|
253
|
+
user exists (verified by test).
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Section 9: Authentication & User Lifecycle
|
|
258
|
+
|
|
259
|
+
Read `references/auth-patterns.md`. Must include, with complete samples:
|
|
260
|
+
|
|
261
|
+
- scrypt hash/verify (`node:crypto`, N=2^15, r=8, p=1, `salt:hash`, `timingSafeEqual`)
|
|
262
|
+
- Password policy Zod schema in `core/` (min 10 chars, reject username-as-password, no
|
|
263
|
+
composition rules) shared by UI and API
|
|
264
|
+
- Session issue/resolve/revoke: 32-byte random token, SHA-256 hash stored, sliding
|
|
265
|
+
expiry, 30-day absolute cap; cookie `httpOnly` `SameSite=Lax` `Path=/`, `Secure` only
|
|
266
|
+
over HTTPS
|
|
267
|
+
- In-memory login rate limiter (fixed window, 10 / 15 min per username+IP)
|
|
268
|
+
- Forced-change middleware returning `403 PASSWORD_CHANGE_REQUIRED` on every route except
|
|
269
|
+
login / change-password / logout
|
|
270
|
+
- The user management flow table (create / forced change / change / admin reset /
|
|
271
|
+
delete with last-admin invariant) and the MUST-NOT list (no email, registration,
|
|
272
|
+
OAuth, JWT)
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Section 10: Web SPA Shell
|
|
277
|
+
|
|
278
|
+
Must include:
|
|
279
|
+
|
|
280
|
+
- `packages/web/vite.config.ts` (dev proxy to the server port; build output consumed by
|
|
281
|
+
the publish pipeline), Tailwind v4 CSS-first setup (`@import "tailwindcss"` + `@theme`
|
|
282
|
+
tokens extracted from the mockups/design system), vendored shadcn/ui conventions
|
|
283
|
+
- `web/src/lib/api.ts` — typed `hono/client` RPC client with the 403
|
|
284
|
+
`PASSWORD_CHANGE_REQUIRED` interceptor hard-redirecting to `/force-change-password` —
|
|
285
|
+
complete sample
|
|
286
|
+
- Router setup with the mandatory routes (`/login`, `/force-change-password`, `/users`,
|
|
287
|
+
`/settings`) plus per-module routes; role-guarded navigation derived from mockups
|
|
288
|
+
- Shared layout (sidebar + topbar + footer rendering `v{{VERSION}}` from
|
|
289
|
+
`/api/system/info`)
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Section 11: Testing Strategy
|
|
294
|
+
|
|
295
|
+
Read `references/testing-patterns.md`. Must include the Vitest setup (`:memory:` SQLite
|
|
296
|
+
via the connection factory with real migrations — never mock the DB), the mandatory
|
|
297
|
+
coverage list (forced-change gating on every guarded route, last-admin invariant, session
|
|
298
|
+
revocation on password change, rate limiting, bootstrap no-op on second start), the
|
|
299
|
+
`scripts/smoke-start.mjs` complete sample, and the Playwright E2E scenario (first-run
|
|
300
|
+
login with printed credentials → forced change → create second user → second user's
|
|
301
|
+
forced change → password change from settings).
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Section 12: Packaging & Distribution
|
|
306
|
+
|
|
307
|
+
npm-only distribution: `files` whitelist, `engines`, `bin`, `prepublishOnly` runs
|
|
308
|
+
`pnpm verify`, semantic versioning guidance, the `npm pack` → global-install →
|
|
309
|
+
smoke-sequence pre-publish check, and the note that daemonization/TLS are the operator's
|
|
310
|
+
concern (systemd/pm2/reverse proxy examples in prose, never in code).
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
# Part B: Per-Module `<module-name>/SPEC.md`
|
|
315
|
+
|
|
316
|
+
Every module file is **self-contained** — a coding agent implements it independently
|
|
317
|
+
after the shared infrastructure exists. `user-management/SPEC.md` is always generated
|
|
318
|
+
and follows this same template with the auth blueprint as its content.
|
|
319
|
+
|
|
320
|
+
## Template
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
# {{MODULE_NAME}} — Module Specification
|
|
324
|
+
|
|
325
|
+
> Part of [{{APPLICATION_NAME}} Technical Specification](../SPECIFICATION.md).
|
|
326
|
+
> Implement after the shared infrastructure (root spec sections 2–10) is in place.
|
|
327
|
+
|
|
328
|
+
## 1. Traceability
|
|
329
|
+
|
|
330
|
+
### User Stories
|
|
331
|
+
| ID | Version | Summary | Implemented By |
|
|
332
|
+
|----|---------|---------|----------------|
|
|
333
|
+
| {{USXX00001}} | [v1.0.0] | {{summary}} | {{routes.ts handler / page component}} |
|
|
334
|
+
|
|
335
|
+
### Non-Functional Requirements
|
|
336
|
+
| ID | Version | Requirement | Technical Decision |
|
|
337
|
+
|----|---------|-------------|--------------------|
|
|
338
|
+
|
|
339
|
+
### Constraints
|
|
340
|
+
| ID | Version | Constraint | Enforcement Point |
|
|
341
|
+
|----|---------|------------|-------------------|
|
|
342
|
+
|
|
343
|
+
### Mockup Screens
|
|
344
|
+
| Screen | Role | SPA Route | Page Component |
|
|
345
|
+
|--------|------|-----------|----------------|
|
|
346
|
+
|
|
347
|
+
### Removed / Replaced
|
|
348
|
+
| Removed ID | Removed In | Replaced By | Reason |
|
|
349
|
+
|-----------|------------|-------------|--------|
|
|
350
|
+
|
|
351
|
+
## 2. Data Model (Drizzle)
|
|
352
|
+
The module's tables in server/src/db/schema.ts — field-for-field from the model files,
|
|
353
|
+
with relations and the generated migration filename.
|
|
354
|
+
|
|
355
|
+
## 3. Shared Schemas (packages/core)
|
|
356
|
+
core/src/schemas/{{domain}}.ts — Zod schemas for entities, create/update inputs, and
|
|
357
|
+
query params; exported types via z.infer<>. Complete sample.
|
|
358
|
+
|
|
359
|
+
## 4. Service (packages/server)
|
|
360
|
+
features/{{module}}/service.ts — pure functions taking the db handle; business rules
|
|
361
|
+
from constraints as explicit invariants. No HTTP concerns, no terminal I/O.
|
|
362
|
+
Complete sample.
|
|
363
|
+
|
|
364
|
+
## 5. Routes (packages/server)
|
|
365
|
+
features/{{module}}/routes.ts — Hono sub-app with @hono/zod-validator on every body/
|
|
366
|
+
query/param, role guards, and typed responses; mounted in app.ts under /api/{{module}}.
|
|
367
|
+
Complete sample. Route table: method, path, guard, request schema, response type,
|
|
368
|
+
story IDs.
|
|
369
|
+
|
|
370
|
+
## 6. SPA Feature (packages/web)
|
|
371
|
+
- Page components under web/src/routes/ — one per mockup screen, shadcn/ui components
|
|
372
|
+
matching the mockup layout ([If DataGrid] shadcn Data Table / TanStack Table;
|
|
373
|
+
[If DatePickers] react-day-picker; [If RichText] Tiptap)
|
|
374
|
+
- Forms with the shared core/ Zod schemas
|
|
375
|
+
- Typed data access via the hono/client RPC client (no hand-written fetch paths)
|
|
376
|
+
- Route definitions + role-guarded navigation entries
|
|
377
|
+
Complete samples for every component.
|
|
378
|
+
|
|
379
|
+
## 7. Tests
|
|
380
|
+
- Vitest: service tests + route tests over :memory: with real migrations; every
|
|
381
|
+
constraint invariant has a test
|
|
382
|
+
- Playwright: the module's user-visible flows appended to the E2E suite
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
## Special case: `user-management/SPEC.md`
|
|
386
|
+
|
|
387
|
+
Same template; its content comes from `references/auth-patterns.md`:
|
|
388
|
+
|
|
389
|
+
- Tables: `users`, `sessions` (Section 2)
|
|
390
|
+
- Schemas: password policy, login input, user create/update (Section 3)
|
|
391
|
+
- Service: bootstrap, hashing, session lifecycle, user CRUD with last-admin invariant
|
|
392
|
+
(Section 4)
|
|
393
|
+
- Routes: `POST /api/auth/login`, `POST /api/auth/logout`,
|
|
394
|
+
`POST /api/auth/change-password`, `GET /api/auth/me`, `GET/POST/PATCH/DELETE
|
|
395
|
+
/api/users...` with admin guards (Section 5)
|
|
396
|
+
- SPA: `/login`, `/force-change-password`, `/users`, `/settings` pages (Section 6)
|
|
397
|
+
- Tests: the mandatory security coverage list (Section 7)
|
|
398
|
+
|
|
399
|
+
When PRD.md defines its own auth/user stories, merge their IDs into this file's
|
|
400
|
+
traceability tables. PRD phrasing never weakens the security invariants — flag conflicts
|
|
401
|
+
to the user instead.
|