teamshare-bridge 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge/daemon.js +36 -0
- package/dist/bridge/daemon.js.map +1 -1
- package/dist/bridge/local-server.d.ts +1 -1
- package/dist/bridge/spawn.d.ts +2 -0
- package/dist/bridge/spawn.js +17 -0
- package/dist/bridge/spawn.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +1 -0
- package/dist/cli/commands/audit.js +75 -0
- package/dist/cli/commands/audit.js.map +1 -0
- package/dist/cli/commands/build.js +9 -6
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/doc.js +1 -1
- package/dist/cli/commands/doc.js.map +1 -1
- package/dist/cli/commands/run.js +3 -2
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +1 -0
- package/dist/cli/commands/scaffold.js +111 -0
- package/dist/cli/commands/scaffold.js.map +1 -0
- package/dist/cli/commands/worker.d.ts +2 -0
- package/dist/cli/commands/worker.js +110 -0
- package/dist/cli/commands/worker.js.map +1 -0
- package/dist/cli/index.js +16 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/lib/api.d.ts +22 -3
- package/dist/lib/api.js +9 -2
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/audit/docs.d.ts +5 -0
- package/dist/lib/audit/docs.js +52 -0
- package/dist/lib/audit/docs.js.map +1 -0
- package/dist/lib/audit/errors.d.ts +15 -0
- package/dist/lib/audit/errors.js +154 -0
- package/dist/lib/audit/errors.js.map +1 -0
- package/dist/lib/audit/gates.d.ts +2 -0
- package/dist/lib/audit/gates.js +57 -0
- package/dist/lib/audit/gates.js.map +1 -0
- package/dist/lib/audit/modularity.d.ts +13 -0
- package/dist/lib/audit/modularity.js +200 -0
- package/dist/lib/audit/modularity.js.map +1 -0
- package/dist/lib/audit/quality.d.ts +9 -0
- package/dist/lib/audit/quality.js +128 -0
- package/dist/lib/audit/quality.js.map +1 -0
- package/dist/lib/audit/rbac.d.ts +14 -0
- package/dist/lib/audit/rbac.js +139 -0
- package/dist/lib/audit/rbac.js.map +1 -0
- package/dist/lib/audit/reporter.d.ts +5 -0
- package/dist/lib/audit/reporter.js +107 -0
- package/dist/lib/audit/reporter.js.map +1 -0
- package/dist/lib/audit/runner.d.ts +2 -0
- package/dist/lib/audit/runner.js +150 -0
- package/dist/lib/audit/runner.js.map +1 -0
- package/dist/lib/audit/security.d.ts +9 -0
- package/dist/lib/audit/security.js +151 -0
- package/dist/lib/audit/security.js.map +1 -0
- package/dist/lib/audit/types.d.ts +67 -0
- package/dist/lib/audit/types.js +26 -0
- package/dist/lib/audit/types.js.map +1 -0
- package/dist/lib/audit/ux.d.ts +9 -0
- package/dist/lib/audit/ux.js +112 -0
- package/dist/lib/audit/ux.js.map +1 -0
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/llm.js +1 -0
- package/dist/lib/llm.js.map +1 -1
- package/dist/lib/lock.d.ts +1 -1
- package/dist/lib/scaffold/renderer.d.ts +7 -0
- package/dist/lib/scaffold/renderer.js +37 -0
- package/dist/lib/scaffold/renderer.js.map +1 -0
- package/dist/lib/scaffold/structure.d.ts +3 -0
- package/dist/lib/scaffold/structure.js +73 -0
- package/dist/lib/scaffold/structure.js.map +1 -0
- package/dist/lib/scaffold/types.d.ts +34 -0
- package/dist/lib/scaffold/types.js +6 -0
- package/dist/lib/scaffold/types.js.map +1 -0
- package/dist/lib/scaffold/verify.d.ts +9 -0
- package/dist/lib/scaffold/verify.js +48 -0
- package/dist/lib/scaffold/verify.js.map +1 -0
- package/dist/lib/skills.d.ts +2 -2
- package/dist/lib/skills.js +2 -1
- package/dist/lib/skills.js.map +1 -1
- package/package.json +3 -2
- package/skills/teamshare-orchestrator-ask/SKILL.md +14 -0
- package/skills/teamshare-scaffold/SKILL.md +133 -0
- package/templates/backend/.env.example.tpl +13 -0
- package/templates/backend/nest-cli.json.tpl +8 -0
- package/templates/backend/package.json.tpl +41 -0
- package/templates/backend/prisma/schema.prisma.tpl +39 -0
- package/templates/backend/src/app.module.ts.tpl +24 -0
- package/templates/backend/src/common/filters/all-exceptions.filter.ts.tpl +59 -0
- package/templates/backend/src/common/guards/jwt-auth.guard.ts.tpl +27 -0
- package/templates/backend/src/common/guards/rbac.guard.ts.tpl +48 -0
- package/templates/backend/src/common/interceptors/transform.interceptor.ts.tpl +44 -0
- package/templates/backend/src/common/pipes/zod-validation.pipe.ts.tpl +29 -0
- package/templates/backend/src/common/swagger/envelope.schemas.ts.tpl +53 -0
- package/templates/backend/src/common/swagger/error.schemas.ts.tpl +62 -0
- package/templates/backend/src/main.ts.tpl +27 -0
- package/templates/backend/src/modules/example/example.controller.ts.tpl +102 -0
- package/templates/backend/src/modules/example/example.dto.ts.tpl +14 -0
- package/templates/backend/src/modules/example/example.module.ts.tpl +10 -0
- package/templates/backend/src/modules/example/example.service.ts.tpl +45 -0
- package/templates/backend/tsconfig.json.tpl +26 -0
- package/templates/common/.gitignore.tpl +42 -0
- package/templates/common/AGENTS.md.tpl +58 -0
- package/templates/common/docs/adr/README.md +25 -0
- package/templates/common/docs/api-contract.md.tpl +72 -0
- package/templates/web/next.config.ts.tpl +7 -0
- package/templates/web/package.json.tpl +37 -0
- package/templates/web/src/app/globals.css.tpl +48 -0
- package/templates/web/src/app/layout.tsx.tpl +25 -0
- package/templates/web/src/components/shared/app-button.tsx.tpl +59 -0
- package/templates/web/src/components/shared/app-empty-state.tsx.tpl +39 -0
- package/templates/web/src/components/shared/app-list-toolbar.tsx.tpl +63 -0
- package/templates/web/src/components/shared/app-skeleton.tsx.tpl +14 -0
- package/templates/web/src/components/shared/app-table.tsx.tpl +128 -0
- package/templates/web/src/components/shared/index.ts.tpl +5 -0
- package/templates/web/src/hooks/use-list-state.ts.tpl +72 -0
- package/templates/web/src/hooks/use-url-tab.ts.tpl +25 -0
- package/templates/web/src/lib/api/client.ts.tpl +123 -0
- package/templates/web/src/lib/providers.tsx.tpl +35 -0
- package/templates/web/src/lib/query-keys.ts.tpl +21 -0
- package/templates/web/src/lib/utils.ts.tpl +7 -0
- package/templates/web/src/lib/validation/schemas.ts.tpl +15 -0
- package/templates/web/src/modules/example/views/example-list-view.tsx.tpl +98 -0
- package/templates/web/tsconfig.json.tpl +23 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifyScaffold = verifyScaffold;
|
|
4
|
+
/**
|
|
5
|
+
* Scaffold verification — runs build/typecheck on the scaffolded project.
|
|
6
|
+
*/
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
function verifyScaffold(rootPath, type) {
|
|
9
|
+
const steps = [];
|
|
10
|
+
// Install dependencies
|
|
11
|
+
steps.push(runStep(rootPath, 'npm install', 'install'));
|
|
12
|
+
// Type-specific verification
|
|
13
|
+
if (type === 'web' || type === 'full') {
|
|
14
|
+
steps.push(runStep(rootPath, 'npm run build', 'next-build'));
|
|
15
|
+
}
|
|
16
|
+
if (type === 'backend' || type === 'full') {
|
|
17
|
+
steps.push(runStep(rootPath, 'npx prisma generate', 'prisma-generate'));
|
|
18
|
+
steps.push(runStep(rootPath, 'npm run build', 'nest-build'));
|
|
19
|
+
}
|
|
20
|
+
if (type === 'mobile') {
|
|
21
|
+
steps.push(runStep(rootPath, 'npx expo export --dump-sourcemap', 'expo-export'));
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
passed: steps.every((s) => s.passed),
|
|
25
|
+
steps,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function runStep(cwd, command, name) {
|
|
29
|
+
try {
|
|
30
|
+
(0, node_child_process_1.execSync)(command, {
|
|
31
|
+
cwd,
|
|
32
|
+
timeout: 120_000,
|
|
33
|
+
encoding: 'utf-8',
|
|
34
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
35
|
+
env: { ...process.env, CI: 'true' },
|
|
36
|
+
});
|
|
37
|
+
return { name, passed: true };
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const e = err;
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
passed: false,
|
|
44
|
+
error: (e.stderr ?? e.message ?? 'Unknown error').slice(0, 1000),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../../src/lib/scaffold/verify.ts"],"names":[],"mappings":";;AAUA,wCAsBC;AAhCD;;GAEG;AACH,2DAA8C;AAO9C,SAAgB,cAAc,CAAC,QAAgB,EAAE,IAAY;IAC3D,MAAM,KAAK,GAAwD,EAAE,CAAC;IAEtE,uBAAuB;IACvB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC;IAExD,6BAA6B;IAC7B,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACtC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,eAAe,EAAE,YAAY,CAAC,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QAC1C,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,qBAAqB,EAAE,iBAAiB,CAAC,CAAC,CAAC;QACxE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,eAAe,EAAE,YAAY,CAAC,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,kCAAkC,EAAE,aAAa,CAAC,CAAC,CAAC;IACnF,CAAC;IAED,OAAO;QACL,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;QACpC,KAAK;KACN,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,GAAW,EAAE,OAAe,EAAE,IAAY;IACzD,IAAI,CAAC;QACH,IAAA,6BAAQ,EAAC,OAAO,EAAE;YAChB,GAAG;YACH,OAAO,EAAE,OAAO;YAChB,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;YAC/B,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE;SACpC,CAAC,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAChC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,GAA4C,CAAC;QACvD,OAAO;YACL,IAAI;YACJ,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,IAAI,eAAe,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC;SACjE,CAAC;IACJ,CAAC;AACH,CAAC"}
|
package/dist/lib/skills.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** The
|
|
2
|
-
export declare const SHIPPED_SKILLS: readonly ["teamshare-cdd", "teamshare-modularity", "teamshare-auth-rbac", "teamshare-orchestrator-ask"];
|
|
1
|
+
/** The five skills shipped with the bridge (source of truth: ./skills). */
|
|
2
|
+
export declare const SHIPPED_SKILLS: readonly ["teamshare-cdd", "teamshare-modularity", "teamshare-auth-rbac", "teamshare-orchestrator-ask", "teamshare-scaffold"];
|
|
3
3
|
/** Source: <package>/skills (ships in the npm tarball via package.json files). */
|
|
4
4
|
export declare function skillsSourceDir(): string;
|
|
5
5
|
/** Target: ~/.agents/skills (opencode convention) unless overridden. */
|
package/dist/lib/skills.js
CHANGED
|
@@ -16,12 +16,13 @@ exports.uninstallSkills = uninstallSkills;
|
|
|
16
16
|
const node_fs_1 = require("node:fs");
|
|
17
17
|
const node_os_1 = require("node:os");
|
|
18
18
|
const node_path_1 = require("node:path");
|
|
19
|
-
/** The
|
|
19
|
+
/** The five skills shipped with the bridge (source of truth: ./skills). */
|
|
20
20
|
exports.SHIPPED_SKILLS = [
|
|
21
21
|
'teamshare-cdd',
|
|
22
22
|
'teamshare-modularity',
|
|
23
23
|
'teamshare-auth-rbac',
|
|
24
24
|
'teamshare-orchestrator-ask',
|
|
25
|
+
'teamshare-scaffold',
|
|
25
26
|
];
|
|
26
27
|
/** Source: <package>/skills (ships in the npm tarball via package.json files). */
|
|
27
28
|
function skillsSourceDir() {
|
package/dist/lib/skills.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/lib/skills.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/lib/skills.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
2
|
"name": "teamshare-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Local bridge for TeamShare agents - CLI session runner, auto-wake WebSocket daemon, and teamshare:// protocol handler.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
21
21
|
"skills",
|
|
22
|
+
"templates",
|
|
22
23
|
"tools",
|
|
23
24
|
"README.md"
|
|
24
25
|
],
|
|
@@ -61,3 +61,17 @@ Humans can answer from two places:
|
|
|
61
61
|
2. **#agent-questions chat**: answer in the project's agent-questions channel
|
|
62
62
|
|
|
63
63
|
Both surfaces trigger the same `question.answered` wake event.
|
|
64
|
+
|
|
65
|
+
## Merge conflicts (IMP-810)
|
|
66
|
+
|
|
67
|
+
When two agents edit the same file and their branches conflict during merge:
|
|
68
|
+
1. The orchestrator blocks the task (`status: 'blocked'`, `workerStatus: 'blocked'`)
|
|
69
|
+
2. A `[question]` comment is posted on the linked task listing the conflicting files
|
|
70
|
+
3. The OT transitions to `question_pending`
|
|
71
|
+
4. A human resolves the conflicts (manually or by telling an agent what to do)
|
|
72
|
+
5. When the question is answered, the OT requeues and the worker retries
|
|
73
|
+
|
|
74
|
+
If you are the worker agent and see a merge conflict:
|
|
75
|
+
- Do NOT force-push or overwrite the integration branch
|
|
76
|
+
- Wait for the human to resolve or give you instructions
|
|
77
|
+
- When re-dispatched, pull the latest integration branch and re-apply your changes
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: teamshare-scaffold
|
|
3
|
+
description: "Enforce TeamShare standards when scaffolding new codebases, auditing existing ones, or cleaning up code. Covers stack decisions, folder layouts, view recipes, RBAC guards, loading states, safety patterns (no lint-disable unless worse), and the audit report format."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# teamshare-scaffold
|
|
7
|
+
|
|
8
|
+
Standards enforcement for TeamShare codebases. Use when scaffolding a new repo, auditing an existing codebase, or cleaning up code.
|
|
9
|
+
|
|
10
|
+
## Stack Decisions
|
|
11
|
+
|
|
12
|
+
| Layer | Stack |
|
|
13
|
+
|-------|-------|
|
|
14
|
+
| Web | Next.js App Router + TS + Tailwind v4 + shadcn (radix-nova) + TS* wrappers |
|
|
15
|
+
| Icons | iconsax-react (variant+color always, no emoji) |
|
|
16
|
+
| Tables | @tanstack/react-table v8 (NOT v9) |
|
|
17
|
+
| Forms | TSForm + zodResolver + zod schemas mirroring backend |
|
|
18
|
+
| State | useListState + useUrlTab (URL-driven) |
|
|
19
|
+
| Backend | NestJS 11 + Prisma 6 + zod-first DTOs + envelope |
|
|
20
|
+
| Mobile | Expo SDK 57 + NativeWind v4 + reusables |
|
|
21
|
+
|
|
22
|
+
## Frontend Layout
|
|
23
|
+
|
|
24
|
+
- `src/components/ui/` = VENDOR shadcn. DO NOT EDIT. DO NOT IMPORT from app code.
|
|
25
|
+
- `src/components/shared/` = Shared wrappers (AppButton, AppTable, etc). Barrel index.ts.
|
|
26
|
+
- `src/modules/<feature>/{views,components,columns,hooks,utils}`
|
|
27
|
+
- `src/lib/api/<feature>.ts` + `src/lib/query-keys.ts` + `src/lib/validation/schemas.ts`
|
|
28
|
+
- Pages stay thin. Views do the work.
|
|
29
|
+
|
|
30
|
+
## Backend Layout
|
|
31
|
+
|
|
32
|
+
- `src/modules/<feature>/{controller,service,dto,module}`
|
|
33
|
+
- `src/common/{guards,filters,interceptors,pipes,swagger,constants,utils,prisma,logging}`
|
|
34
|
+
- Prisma schema.prisma is canonical. `npx prisma migrate deploy` only.
|
|
35
|
+
|
|
36
|
+
## View Recipe (every list view)
|
|
37
|
+
|
|
38
|
+
1. `useListState({ pageSize })` - URL-driven
|
|
39
|
+
2. `useQuery({ queryKey: qkList(qk.x, list.query), queryFn: () => api.list(token, list.query) })`
|
|
40
|
+
3. Loading: `AppSkeleton` rows (3-6)
|
|
41
|
+
4. Empty: `AppTable` with emptyTitle + emptyDescription + emptyAction (create CTA)
|
|
42
|
+
5. Error: `ErrorState` with retry
|
|
43
|
+
6. Toolbar: `AppListToolbar` with actions slot (create button always visible)
|
|
44
|
+
7. Mutations: `useMutation` + toast success/error + `queryClient.invalidateQueries`
|
|
45
|
+
|
|
46
|
+
Every view MUST handle loading, error, and empty states. Never show white screen.
|
|
47
|
+
|
|
48
|
+
## RBAC Guard Table
|
|
49
|
+
|
|
50
|
+
| Who | Guard stack |
|
|
51
|
+
|-----|-------------|
|
|
52
|
+
| Human only | `@Permissions('entity.action')` |
|
|
53
|
+
| Agent + human | `@Public()` + `AgentsDualAuthGuard` + `@ApiKeyScopes` |
|
|
54
|
+
| Truly public | `@Public()` only |
|
|
55
|
+
| MCP | `ApiKeyGuard` + `canUseTool()` |
|
|
56
|
+
|
|
57
|
+
Never combine @Permissions with AgentsDualAuthGuard on same route.
|
|
58
|
+
|
|
59
|
+
Three enforcement layers: capability + key scope + project access. Never less than two.
|
|
60
|
+
|
|
61
|
+
## IconSax Rules
|
|
62
|
+
|
|
63
|
+
Every icon instance MUST pass `variant` and `color` props. Default variant="Outline".
|
|
64
|
+
- Bold for active nav
|
|
65
|
+
- TwoTone for empty-state illustrations
|
|
66
|
+
- Broken for destructive confirmations
|
|
67
|
+
- Sizes: 16/20/24/32
|
|
68
|
+
|
|
69
|
+
## Safety Patterns
|
|
70
|
+
|
|
71
|
+
1. **eslint-disable only when fix is worse.** Add `// eslint-disable-next-line <rule> -- <reason>`. Inventoried for revert.
|
|
72
|
+
2. **Never hand-edit generated/ or vendor ui/.**
|
|
73
|
+
3. **Build/typecheck verified.** Failed fixes reverted + noted.
|
|
74
|
+
4. **Cleanup in worktrees/branches.**
|
|
75
|
+
5. **Secrets never in code/logs/messages.**
|
|
76
|
+
6. **Existing repos: additive fixes only.** Never restructure.
|
|
77
|
+
7. **Max 120 chars per line.**
|
|
78
|
+
|
|
79
|
+
## Audit Report Format
|
|
80
|
+
|
|
81
|
+
When running an audit, produce findings in this structure:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
# Audit Report — [repo-name]
|
|
85
|
+
|
|
86
|
+
**Date:** YYYY-MM-DD
|
|
87
|
+
**Scope:** [files scanned]
|
|
88
|
+
|
|
89
|
+
## Critical / Security
|
|
90
|
+
- [finding] [file:line] [evidence] [suggested fix]
|
|
91
|
+
|
|
92
|
+
## Modularity
|
|
93
|
+
- [finding] [file:line] [evidence] [suggested fix]
|
|
94
|
+
|
|
95
|
+
## Quality
|
|
96
|
+
- [finding] [file:line] [evidence] [suggested fix]
|
|
97
|
+
|
|
98
|
+
## Advisory (existing code)
|
|
99
|
+
- [finding] [file:line] [note]
|
|
100
|
+
|
|
101
|
+
## Summary
|
|
102
|
+
- Total findings: N (Critical: X, Security: Y, Modularity: Z, Quality: W)
|
|
103
|
+
- Gate status: build [pass/fail], typecheck [pass/fail]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Severity levels: Critical (security, data loss), High (broken patterns, missing guards),
|
|
107
|
+
Medium (quality, missing states), Low (style, naming), Advisory (existing code deviations).
|
|
108
|
+
|
|
109
|
+
## Scaffold Task Flow
|
|
110
|
+
|
|
111
|
+
When asked to scaffold a new codebase:
|
|
112
|
+
1. Ask questions first (stack, auth provider, DB, package manager) if --ask
|
|
113
|
+
2. Write plan.md before generating
|
|
114
|
+
3. Create folder tree from template
|
|
115
|
+
4. Write AGENTS.md + docs/ skeleton
|
|
116
|
+
5. Generate module skeletons with full guard stack + loading states
|
|
117
|
+
6. Run build/typecheck gates
|
|
118
|
+
7. Write summary.md with what was built
|
|
119
|
+
|
|
120
|
+
When asked to audit an existing codebase:
|
|
121
|
+
1. Scan all dimensions (security, modularity, rbac, errors, quality, ux, docs, gates)
|
|
122
|
+
2. Existing code deviations = Advisory only (never force restructure)
|
|
123
|
+
3. New code deviations = High/Critical findings
|
|
124
|
+
4. Produce detailed markdown report with file:line evidence
|
|
125
|
+
5. Suggest fixes for each finding
|
|
126
|
+
|
|
127
|
+
When asked to clean up code:
|
|
128
|
+
1. Run audit first
|
|
129
|
+
2. Group findings into fix tasks
|
|
130
|
+
3. Apply mechanical fixes (line length, naming, imports)
|
|
131
|
+
4. For complex fixes: ask_human or route to orchestrator
|
|
132
|
+
5. Every fix verified by build/typecheck
|
|
133
|
+
6. Failed fixes reverted + noted in report
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Database
|
|
2
|
+
DATABASE_URL="postgresql://user:password@localhost:5432/{{slug}}?schema=public"
|
|
3
|
+
|
|
4
|
+
# JWT
|
|
5
|
+
JWT_SECRET="change-me-to-a-random-secret"
|
|
6
|
+
JWT_EXPIRES_IN="7d"
|
|
7
|
+
|
|
8
|
+
# Server
|
|
9
|
+
PORT={{apiPort}}
|
|
10
|
+
NODE_ENV=development
|
|
11
|
+
|
|
12
|
+
# CORS
|
|
13
|
+
CORS_ORIGIN="http://localhost:{{port}}"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{packageName}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "{{description}}",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "nest build",
|
|
8
|
+
"start": "nest start",
|
|
9
|
+
"start:dev": "nest start --watch",
|
|
10
|
+
"prisma:generate": "prisma generate",
|
|
11
|
+
"prisma:migrate": "prisma migrate dev",
|
|
12
|
+
"lint": "eslint src"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@nestjs/common": "^11.0.0",
|
|
16
|
+
"@nestjs/config": "^4.0.0",
|
|
17
|
+
"@nestjs/core": "^11.0.0",
|
|
18
|
+
"@nestjs/passport": "^11.0.0",
|
|
19
|
+
"@nestjs/platform-express": "^11.0.0",
|
|
20
|
+
"@nestjs/swagger": "^11.0.0",
|
|
21
|
+
"@prisma/client": "^6.0.0",
|
|
22
|
+
"argon2": "^0.41.0",
|
|
23
|
+
"class-transformer": "^0.5.1",
|
|
24
|
+
"class-validator": "^0.14.0",
|
|
25
|
+
"passport": "^0.7.0",
|
|
26
|
+
"passport-jwt": "^4.0.1",
|
|
27
|
+
"prisma-zod-generator": "^0.9.0",
|
|
28
|
+
"reflect-metadata": "^0.2.0",
|
|
29
|
+
"rxjs": "^7.8.0",
|
|
30
|
+
"socket.io": "^4.8.0",
|
|
31
|
+
"zod": "^3.23.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@nestjs/cli": "^11.0.0",
|
|
35
|
+
"@nestjs/schematics": "^11.0.0",
|
|
36
|
+
"@types/node": "^22.0.0",
|
|
37
|
+
"@types/passport-jwt": "^4.0.0",
|
|
38
|
+
"prisma": "^6.0.0",
|
|
39
|
+
"typescript": "^5.6.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
generator client {
|
|
2
|
+
provider = "prisma-client-js"
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
generator zod {
|
|
6
|
+
provider = "prisma-zod-generator"
|
|
7
|
+
output = "../src/generated/zod"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
datasource db {
|
|
11
|
+
provider = "postgresql"
|
|
12
|
+
url = env("DATABASE_URL")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
model User {
|
|
16
|
+
id String @id @default(uuid()) @db.Uuid
|
|
17
|
+
email String @unique
|
|
18
|
+
name String?
|
|
19
|
+
password String
|
|
20
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
21
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
22
|
+
|
|
23
|
+
apiKey ApiKey[]
|
|
24
|
+
|
|
25
|
+
@@map("users")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
model ApiKey {
|
|
29
|
+
id String @id @default(uuid()) @db.Uuid
|
|
30
|
+
key String @unique
|
|
31
|
+
name String
|
|
32
|
+
userId String @map("user_id") @db.Uuid
|
|
33
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
34
|
+
lastUsed DateTime? @map("last_used")
|
|
35
|
+
|
|
36
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
37
|
+
|
|
38
|
+
@@map("api_keys")
|
|
39
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Module } from "@nestjs/common";
|
|
2
|
+
import { ConfigModule } from "@nestjs/config";
|
|
3
|
+
import { APP_GUARD, APP_FILTER, APP_INTERCEPTOR } from "@nestjs/core";
|
|
4
|
+
import { AuthGuard } from "./common/guards/jwt-auth.guard";
|
|
5
|
+
import { RbacGuard } from "./common/guards/rbac.guard";
|
|
6
|
+
import { AllExceptionsFilter } from "./common/filters/all-exceptions.filter";
|
|
7
|
+
import { TransformInterceptor } from "./common/interceptors/transform.interceptor";
|
|
8
|
+
import { PrismaModule } from "./prisma/prisma.module";
|
|
9
|
+
import { ExampleModule } from "./modules/example/example.module";
|
|
10
|
+
|
|
11
|
+
@Module({
|
|
12
|
+
imports: [
|
|
13
|
+
ConfigModule.forRoot({ isGlobal: true }),
|
|
14
|
+
PrismaModule,
|
|
15
|
+
ExampleModule,
|
|
16
|
+
],
|
|
17
|
+
providers: [
|
|
18
|
+
{ provide: APP_GUARD, useClass: AuthGuard },
|
|
19
|
+
{ provide: APP_GUARD, useClass: RbacGuard },
|
|
20
|
+
{ provide: APP_FILTER, useClass: AllExceptionsFilter },
|
|
21
|
+
{ provide: APP_INTERCEPTOR, useClass: TransformInterceptor },
|
|
22
|
+
],
|
|
23
|
+
})
|
|
24
|
+
export class AppModule {}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ExceptionFilter,
|
|
3
|
+
Catch,
|
|
4
|
+
ArgumentsHost,
|
|
5
|
+
HttpException,
|
|
6
|
+
HttpStatus,
|
|
7
|
+
} from "@nestjs/common";
|
|
8
|
+
import { Response } from "express";
|
|
9
|
+
|
|
10
|
+
@Catch()
|
|
11
|
+
export class AllExceptionsFilter implements ExceptionFilter {
|
|
12
|
+
catch(exception: unknown, host: ArgumentsHost) {
|
|
13
|
+
const ctx = host.switchToHttp();
|
|
14
|
+
const response = ctx.getResponse<Response>();
|
|
15
|
+
|
|
16
|
+
let status = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
17
|
+
let code = "INTERNAL_ERROR";
|
|
18
|
+
let message = "An unexpected error occurred";
|
|
19
|
+
let details: unknown = undefined;
|
|
20
|
+
|
|
21
|
+
if (exception instanceof HttpException) {
|
|
22
|
+
status = exception.getStatus();
|
|
23
|
+
const res = exception.getResponse();
|
|
24
|
+
if (typeof res === "string") {
|
|
25
|
+
message = res;
|
|
26
|
+
} else if (typeof res === "object" && res !== null) {
|
|
27
|
+
const obj = res as Record<string, unknown>;
|
|
28
|
+
message = (obj.message as string) ?? message;
|
|
29
|
+
code = (obj.error as string) ?? code;
|
|
30
|
+
details = obj.details;
|
|
31
|
+
}
|
|
32
|
+
code = this.codeFromStatus(status);
|
|
33
|
+
} else if (exception instanceof Error) {
|
|
34
|
+
message = exception.message;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
response.status(status).json({
|
|
38
|
+
success: false,
|
|
39
|
+
error: {
|
|
40
|
+
code,
|
|
41
|
+
message,
|
|
42
|
+
details,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private codeFromStatus(status: number): string {
|
|
48
|
+
const map: Record<number, string> = {
|
|
49
|
+
400: "BAD_REQUEST",
|
|
50
|
+
401: "UNAUTHORIZED",
|
|
51
|
+
403: "FORBIDDEN",
|
|
52
|
+
404: "NOT_FOUND",
|
|
53
|
+
409: "CONFLICT",
|
|
54
|
+
422: "UNPROCESSABLE_ENTITY",
|
|
55
|
+
429: "TOO_MANY_REQUESTS",
|
|
56
|
+
};
|
|
57
|
+
return map[status] ?? "INTERNAL_ERROR";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
|
|
2
|
+
import { Reflector } from "@nestjs/core";
|
|
3
|
+
import { AuthGuard as PassportAuthGuard } from "@nestjs/passport";
|
|
4
|
+
import { IS_PUBLIC_KEY } from "../decorators/public.decorator";
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class AuthGuard extends PassportAuthGuard("jwt") {
|
|
8
|
+
constructor(private reflector: Reflector) {
|
|
9
|
+
super();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
canActivate(context: ExecutionContext) {
|
|
13
|
+
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
|
14
|
+
context.getHandler(),
|
|
15
|
+
context.getClass(),
|
|
16
|
+
]);
|
|
17
|
+
if (isPublic) return true;
|
|
18
|
+
return super.canActivate(context);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
handleRequest(err: unknown, user: unknown) {
|
|
22
|
+
if (err || !user) {
|
|
23
|
+
throw err ?? new UnauthorizedException("Invalid or missing token");
|
|
24
|
+
}
|
|
25
|
+
return user;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CanActivate,
|
|
3
|
+
ExecutionContext,
|
|
4
|
+
ForbiddenException,
|
|
5
|
+
Injectable,
|
|
6
|
+
} from "@nestjs/common";
|
|
7
|
+
import { Reflector } from "@nestjs/core";
|
|
8
|
+
import { PERMISSIONS_KEY } from "../decorators/permissions.decorator";
|
|
9
|
+
import { IS_PUBLIC_KEY } from "../decorators/public.decorator";
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class RbacGuard implements CanActivate {
|
|
13
|
+
constructor(private reflector: Reflector) {}
|
|
14
|
+
|
|
15
|
+
canActivate(context: ExecutionContext): boolean {
|
|
16
|
+
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
|
17
|
+
context.getHandler(),
|
|
18
|
+
context.getClass(),
|
|
19
|
+
]);
|
|
20
|
+
if (isPublic) return true;
|
|
21
|
+
|
|
22
|
+
const requiredPermissions = this.reflector.getAllAndOverride<string[]>(
|
|
23
|
+
PERMISSIONS_KEY,
|
|
24
|
+
[context.getHandler(), context.getClass()]
|
|
25
|
+
);
|
|
26
|
+
if (!requiredPermissions || requiredPermissions.length === 0) return true;
|
|
27
|
+
|
|
28
|
+
const request = context.switchToHttp().getRequest();
|
|
29
|
+
const user = request.user;
|
|
30
|
+
|
|
31
|
+
if (!user) {
|
|
32
|
+
throw new ForbiddenException("Authentication required");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const userPermissions: string[] = user.permissions ?? [];
|
|
36
|
+
const hasPermission = requiredPermissions.every((p) =>
|
|
37
|
+
userPermissions.includes(p)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (!hasPermission) {
|
|
41
|
+
throw new ForbiddenException(
|
|
42
|
+
`Missing required permissions: ${requiredPermissions.join(", ")}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Injectable,
|
|
3
|
+
NestInterceptor,
|
|
4
|
+
ExecutionContext,
|
|
5
|
+
CallHandler,
|
|
6
|
+
} from "@nestjs/common";
|
|
7
|
+
import { Observable, map } from "rxjs";
|
|
8
|
+
|
|
9
|
+
export interface SuccessResponse<T> {
|
|
10
|
+
success: true;
|
|
11
|
+
data: T;
|
|
12
|
+
pagination?: {
|
|
13
|
+
page: number;
|
|
14
|
+
limit: number;
|
|
15
|
+
total: number;
|
|
16
|
+
totalPages: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Injectable()
|
|
21
|
+
export class TransformInterceptor<T>
|
|
22
|
+
implements NestInterceptor<T, SuccessResponse<T>>
|
|
23
|
+
{
|
|
24
|
+
intercept(
|
|
25
|
+
context: ExecutionContext,
|
|
26
|
+
next: CallHandler
|
|
27
|
+
): Observable<SuccessResponse<T>> {
|
|
28
|
+
return next.handle().pipe(
|
|
29
|
+
map((data) => {
|
|
30
|
+
if (data && typeof data === "object" && "data" in data && "pagination" in data) {
|
|
31
|
+
return {
|
|
32
|
+
success: true as const,
|
|
33
|
+
data: data.data,
|
|
34
|
+
pagination: data.pagination,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
success: true as const,
|
|
39
|
+
data,
|
|
40
|
+
};
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PipeTransform,
|
|
3
|
+
Injectable,
|
|
4
|
+
BadRequestException,
|
|
5
|
+
} from "@nestjs/common";
|
|
6
|
+
import { ZodSchema } from "zod";
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class ZodValidationPipe implements PipeTransform {
|
|
10
|
+
constructor(
|
|
11
|
+
private schema: ZodSchema,
|
|
12
|
+
private source: "body" | "query" | "params" = "body"
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
transform(value: unknown) {
|
|
16
|
+
const result = this.schema.safeParse(value);
|
|
17
|
+
if (!result.success) {
|
|
18
|
+
const issues = result.error.issues.map((i) => ({
|
|
19
|
+
path: i.path.join("."),
|
|
20
|
+
message: i.message,
|
|
21
|
+
}));
|
|
22
|
+
throw new BadRequestException({
|
|
23
|
+
message: "Validation failed",
|
|
24
|
+
details: issues,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return result.data;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
|
|
2
|
+
export function successSchema(dataSchema: Record<string, unknown>) {
|
|
3
|
+
return {
|
|
4
|
+
type: "object",
|
|
5
|
+
properties: {
|
|
6
|
+
success: { type: "boolean", example: true },
|
|
7
|
+
data: dataSchema,
|
|
8
|
+
},
|
|
9
|
+
required: ["success", "data"],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function paginatedSchema(dataSchema: Record<string, unknown>) {
|
|
14
|
+
return {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
success: { type: "boolean", example: true },
|
|
18
|
+
data: {
|
|
19
|
+
type: "array",
|
|
20
|
+
items: dataSchema,
|
|
21
|
+
},
|
|
22
|
+
pagination: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
page: { type: "number", example: 1 },
|
|
26
|
+
limit: { type: "number", example: 20 },
|
|
27
|
+
total: { type: "number", example: 100 },
|
|
28
|
+
totalPages: { type: "number", example: 5 },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
required: ["success", "data", "pagination"],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function errorSchema() {
|
|
37
|
+
return {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
success: { type: "boolean", example: false },
|
|
41
|
+
error: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
code: { type: "string", example: "NOT_FOUND" },
|
|
45
|
+
message: { type: "string", example: "Resource not found" },
|
|
46
|
+
details: {},
|
|
47
|
+
},
|
|
48
|
+
required: ["code", "message"],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ["success", "error"],
|
|
52
|
+
};
|
|
53
|
+
}
|