momo-ai 1.0.70 → 1.0.71

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "momo-ai",
3
- "version": "1.0.70",
3
+ "version": "1.0.71",
4
4
  "description": "Rachel Momo ( OpenSpec )",
5
5
  "main": "src/momo.js",
6
6
  "bin": {
@@ -0,0 +1,162 @@
1
+ ---
2
+ name: mo-dev-antd-exform
3
+ description: Use when implementing ADD/EDIT/FILTER forms with ExForm component in React/Ant Design projects using the zero-ui framework
4
+ ---
5
+
6
+ # ExForm Component Patterns
7
+
8
+ ## Overview
9
+
10
+ ExForm provides standardized form handling with three modes (ADD/EDIT/FILTER), automatic validation, i18n integration, and Redux state synchronization.
11
+
12
+ ## When to Use
13
+
14
+ - Building ADD forms for creating new records
15
+ - Building EDIT forms for updating existing records
16
+ - Building FILTER forms for search/query interfaces
17
+ - Need automatic form validation and submission handling
18
+ - Need integration with framework's dialog/notification system
19
+
20
+ ## Form Modes Quick Reference
21
+
22
+ | Mode | yoForm Call | Purpose |
23
+ |------|-------------|---------|
24
+ | ADD | `Ex.yoForm(this, null)` | Create new record |
25
+ | EDIT | `Ex.yoForm(this, null, $inited)` | Edit existing record |
26
+ | FILTER | `Ex.yoFilter(this)` | Filter/search form |
27
+
28
+ ## Hard Rules (No Exceptions)
29
+
30
+ - **Never** use raw Ant Design `<Form>` instead of `<ExForm>`
31
+ - **Never** hardcode form configuration in JSX (use resource JSON `_form` block)
32
+ - **Never** write custom `onFinish` handlers (use `$op` prop with `Ex.form()` API)
33
+ - **Never** skip `@Ux.zero` decorator with `Ux.rxEtat` builder
34
+ - **Never** forget `.form()` in decorator for FILTER mode
35
+ - **Never** forget `.raft(1)` for single-column FILTER layout
36
+ - **Never** pass `$inited` to ADD mode (only EDIT needs it)
37
+
38
+ ## Basic Pattern
39
+
40
+ ```jsx
41
+ import {ExForm} from "ei";
42
+
43
+ // ADD mode
44
+ const form = Ex.yoForm(this, null);
45
+ return <ExForm {...form} $height="300px" $op={Op.actions}/>;
46
+
47
+ // EDIT mode with data
48
+ const {$inited = {}} = this.props;
49
+ const form = Ex.yoForm(this, null, $inited);
50
+ return <ExForm {...form} $height="300px" $op={Op.actions}/>;
51
+
52
+ // FILTER mode
53
+ const form = Ex.yoFilter(this);
54
+ return <ExForm {...form} $op={Op.actions}/>;
55
+ ```
56
+
57
+ ## RxEtat Decorator Configuration
58
+
59
+ ```js
60
+ // ADD form
61
+ @Ux.zero(Ux.rxEtat(require('../Cab'))
62
+ .cab("UI.Add")
63
+ .to()
64
+ )
65
+
66
+ // EDIT form
67
+ @Ux.zero(Ux.rxEtat(require('../Cab'))
68
+ .cab("UI.Edit")
69
+ .to()
70
+ )
71
+
72
+ // FILTER form (single column)
73
+ @Ux.zero(Ux.rxEtat(require('../Cab'))
74
+ .cab("UI.Filter")
75
+ .raft(1) // Single column layout
76
+ .form() // Enable form binding
77
+ .to()
78
+ )
79
+ ```
80
+
81
+ ## Op.js Handler Pattern
82
+
83
+ ```js
84
+ // All handlers MUST have $op prefix
85
+ const $opAdd = (reference) => (params) =>
86
+ Ex.form(reference).add(params, {
87
+ uri: '/api/resource',
88
+ dialog: 'added' // i18n key for success message
89
+ });
90
+
91
+ const $opSave = (reference) => (params) =>
92
+ Ex.form(reference).save(params, {
93
+ uri: '/api/resource/:key',
94
+ dialog: 'saved'
95
+ });
96
+
97
+ const $opFilter = (reference) => (params) =>
98
+ Ex.form(reference).filter(params);
99
+
100
+ export default { $opAdd, $opSave, $opFilter };
101
+ ```
102
+
103
+ ## Post-Operation Redux Chaining
104
+
105
+ ```js
106
+ // Update Redux after save
107
+ const $opAdd = (reference) => (params) =>
108
+ Ex.form(reference).add(params, { uri: "/api/dept", dialog: "added" })
109
+ .then(data => Ux.of(reference)._.ioIn(Ex.K.DEPT, data));
110
+
111
+ // Remove from Redux after delete
112
+ const $opDelete = (reference) => (params) =>
113
+ Ex.form(reference).remove(params, { uri: "/api/dept/:key", dialog: "removed" })
114
+ .then(data => Ux.of(reference)._.ioOut(Ex.K.DEPT, data));
115
+ ```
116
+
117
+ ## Resource JSON Configuration
118
+
119
+ ```json
120
+ {
121
+ "_form": {
122
+ "ui": ["name", "code", "status"],
123
+ "hidden": ["id", "tenantId"],
124
+ "rules": {
125
+ "name": [{ "required": true, "message": "Name is required" }]
126
+ }
127
+ }
128
+ }
129
+ ```
130
+
131
+ ## Common Mistakes
132
+
133
+ | Mistake | Fix |
134
+ |---------|-----|
135
+ | Using raw `<Form>` | Use `<ExForm>` from "ei" |
136
+ | Hardcoding form config in JSX | Define in resource JSON `_form` block |
137
+ | Custom `onFinish` with fetch/axios | Use `$op` prop with `Ex.form().add/save` |
138
+ | Missing `.form()` in FILTER decorator | Add `.form()` to enable form binding |
139
+ | Missing `.raft(1)` for FILTER | Add `.raft(1)` for single-column layout |
140
+ | Passing `$inited` to ADD mode | Only pass to EDIT: `Ex.yoForm(this, null, $inited)` |
141
+ | Op handler without `$op` prefix | All handler keys MUST start with `$op` |
142
+ | Hardcoded success messages | Use `dialog` option with i18n key |
143
+
144
+ ## Red Flags — STOP and Fix
145
+
146
+ - You're using `<Form>` from antd instead of `<ExForm>` from ei
147
+ - You're writing custom form submission logic with fetch/axios
148
+ - You're hardcoding form fields in JSX instead of resource JSON
149
+ - You're missing `@Ux.zero` decorator
150
+ - You're missing `.form()` or `.raft(1)` for FILTER mode
151
+ - Op handler keys don't start with `$op`
152
+
153
+ **All of these mean: Follow the ExForm pattern exactly.**
154
+
155
+ ## Common Rationalizations (and Why They're Wrong)
156
+
157
+ | Excuse | Reality |
158
+ |--------|---------|
159
+ | "Raw Form is more flexible" | ExForm provides validation, i18n, Redux integration automatically |
160
+ | "Hardcoding is faster" | Resource JSON enables reuse and i18n; hardcoding breaks framework |
161
+ | "Custom handlers give more control" | `Ex.form()` API handles dialogs, loading states, Redux updates automatically |
162
+ | "Decorator is too complex" | Decorator wires i18n, Redux, lifecycle; skipping breaks integration |
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: mo-dev-antd-exlist
3
+ description: Use when building list/table pages with ExListFast or ExListComplex components in React/Ant Design projects using the zero-ui framework
4
+ ---
5
+
6
+ # ExList Component Family
7
+
8
+ ## Overview
9
+
10
+ ExList provides two variants for list/table pages with built-in search, pagination, and CRUD operations.
11
+
12
+ ## When to Use
13
+
14
+ - Building list/table pages with search and pagination
15
+ - Need quick list without embedded forms (use ExListFast)
16
+ - Need full CRUD with Add/Edit/Filter forms (use ExListComplex)
17
+ - Displaying data with custom column renderers
18
+ - Implementing batch operations
19
+
20
+ ## Component Variants
21
+
22
+ | Component | Features | Use Case |
23
+ |-----------|----------|----------|
24
+ | `ExListFast` | Search + Pagination + Actions | Quick list without forms |
25
+ | `ExListComplex` | All Fast + Add/Edit/Filter forms | Full CRUD list page |
26
+
27
+ ## Hard Rules (No Exceptions)
28
+
29
+ - **Never** use raw Ant Design `<Table>` instead of ExList components
30
+ - **Never** skip the attributes builder pattern (`yoList()` with `Ex.yoAmbient()`)
31
+ - **Never** use raw `fetch/axios` instead of `Ux.ajaxPost` for search
32
+ - **Never** implement manual pagination logic (use `$options`)
33
+ - **Never** hardcode columns array (use `$renders` pattern)
34
+ - **Never** skip `$executor` pattern for actions
35
+ - **Never** hardcode grid config (load from resource JSON via `Ux.fromHoc`)
36
+
37
+ ## Basic ExListFast Pattern
38
+
39
+ ```jsx
40
+ import {ExListFast} from "ei";
41
+
42
+ <ExListFast {...attrList}
43
+ $renders={columnFn(this)}
44
+ $executor={executeFn(this)}
45
+ rxSearch={query => Ux.ajaxPost("/api/search", query)}
46
+ />
47
+ ```
48
+
49
+ ## Attributes Builder (Op.js)
50
+
51
+ ```js
52
+ const yoList = (reference) => {
53
+ const inherits = Ex.yoAmbient(reference);
54
+
55
+ // Load grid config from resource JSON
56
+ inherits.config = Ux.fromHoc(reference, "grid");
57
+
58
+ // Pagination options
59
+ inherits.$options = {
60
+ pageSize: 20,
61
+ showSizeChanger: true
62
+ };
63
+
64
+ return inherits;
65
+ };
66
+ ```
67
+
68
+ ## Custom Column Renderers
69
+
70
+ ```js
71
+ const columnFn = (reference) => ({
72
+ // Custom text rendering
73
+ name: (text, record) => (
74
+ <a onClick={() => handleEdit(record)}>{text}</a>
75
+ ),
76
+
77
+ // Status tag
78
+ status: (text) => (
79
+ <Tag color={text === 'active' ? 'green' : 'red'}>
80
+ {text === 'active' ? 'Active' : 'Inactive'}
81
+ </Tag>
82
+ ),
83
+
84
+ // Date formatting
85
+ createdAt: (text) => moment(text).format('YYYY-MM-DD HH:mm'),
86
+
87
+ // Actions column
88
+ $op: (text, record) => (
89
+ <Space>
90
+ <a onClick={() => editFn(record)}>Edit</a>
91
+ <a onClick={() => deleteFn(record)}>Delete</a>
92
+ </Space>
93
+ )
94
+ });
95
+ ```
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: mo-dev-antd-lifecycle
3
+ description: Use when creating or updating React/Ant Design pages that must follow the zero-ui yi/yu/yo lifecycle pattern and @Ux.zero decorator
4
+ ---
5
+
6
+ # Page Lifecycle Patterns (yi/yu/yo)
7
+
8
+ ## Overview
9
+
10
+ All Ant Design pages must use the zero-ui lifecycle pattern with `@Ux.zero` decorator, `yi/yu/yo` naming, and `Ex.yoRender` gated by `$ready`.
11
+
12
+ ## When to Use
13
+
14
+ - Creating a new page component
15
+ - Refactoring a page component
16
+ - Fixing page initialization, data loading, or render issues
17
+ - Migrating a function component to zero-ui class component
18
+
19
+ ## Hard Rules (No Exceptions)
20
+
21
+ - **Never** use function components or hooks for pages
22
+ - **Always** use class components with `React.PureComponent`
23
+ - **Always** use `@Ux.zero(Ux.rxEtat(...).to())` decorator
24
+ - **Always** call `Ex.yiAssist(this)` or `Ex.yiStandard(this)` in `componentDidMount`
25
+ - **Always** render with `Ex.yoRender(this, fn, debug)`
26
+ - **Always** gate rendering by `$ready` (handled by `Ex.yoRender`)
27
+ - **Always** use Op handlers with `$op` prefix and `(reference) => (params) => Promise`
28
+
29
+ ## Canonical Pattern
30
+
31
+ ```js
32
+ import Ux from 'ux';
33
+ import Ex from 'ex';
34
+ import Op from './Op';
35
+
36
+ @Ux.zero(Ux.rxEtat(require('./Cab.json'))
37
+ .cab("UI")
38
+ .ready(true)
39
+ .connect(state => Ux.dataIn(state).revamp(["datum"]), true)
40
+ .state({})
41
+ .to()
42
+ )
43
+ class Component extends React.PureComponent {
44
+ componentDidMount() {
45
+ Ex.yiAssist(this); // yi = initialize
46
+ }
47
+ render() {
48
+ return Ex.yoRender(this, () => {
49
+ // render logic here
50
+ }, Ex.parserOfColor("PageName"));
51
+ }
52
+ }
53
+
54
+ export default Component;
55
+ ```
56
+
57
+ ## RxEtat Builder Checklist
58
+
59
+ ```js
60
+ Ux.rxEtat(require('./Cab'))
61
+ .cab("UI")
62
+ .ready(true)
63
+ .state({})
64
+ .connect(s2pFn)
65
+ .bind(OP)
66
+ .to()
67
+ ```
68
+
69
+ ## Op Handler Pattern
70
+
71
+ ```js
72
+ const $opAdd = (reference) => (params) =>
73
+ Ex.form(reference).add(params, {
74
+ uri: '/api/resource',
75
+ dialog: 'added'
76
+ });
77
+
78
+ export default { $opAdd };
79
+ ```
80
+
81
+ ## Common Mistakes
82
+
83
+ | Mistake | Fix |
84
+ |---------|-----|
85
+ | Using hooks/function component | Use class component + @Ux.zero decorator |
86
+ | Missing `Ex.yiAssist` | Call in `componentDidMount` |
87
+ | Rendering without `Ex.yoRender` | Wrap render with `Ex.yoRender` |
88
+ | Missing `$ready` gate | Use `.ready(true)` + `Ex.yoRender` |
89
+ | Op handler without `$op` prefix | Prefix keys with `$op` |
90
+
91
+ ## Red Flags — STOP and Fix
92
+
93
+ - You are writing a function component for a page
94
+ - You are using React hooks in a page
95
+ - You are rendering without `Ex.yoRender`
96
+ - You are missing `@Ux.zero` decorator
97
+ - You are calling `setState` before `$ready`
98
+ - Op handlers are missing `$op` prefix
99
+
100
+ **All of these mean: refactor to the zero-ui lifecycle pattern.**
101
+
102
+ ## Common Rationalizations (and Why They're Wrong)
103
+
104
+ | Excuse | Reality |
105
+ |--------|---------|
106
+ | "Hooks are modern best practice" | Zero-ui framework requires class components for decorator wiring |
107
+ | "Decorator is too complex" | Decorator injects i18n, Redux, lifecycle; skipping breaks framework |
108
+ | "Render directly is fine" | `Ex.yoRender` provides loading/error gate and `$ready` handling |
@@ -0,0 +1,71 @@
1
+ ---
2
+ name: mo-dev-go-backend-route
3
+ description: Use when adding, removing, or moving Go backend modules under internal/apps, internal/system, internal/widgets, or internal/jobs where routes are generated
4
+ ---
5
+
6
+ # Backend Dynamic Routing (Codegen)
7
+
8
+ ## Overview
9
+
10
+ Backend module routing is **compile-time generated**. If you add/move a module and skip codegen, you create missing routes or duplicate route panics.
11
+
12
+ ## When to Use
13
+
14
+ - You add/remove/move a module directory under:
15
+ - `internal/apps/<name>/`
16
+ - `internal/system/<name>/`
17
+ - `internal/widgets/<name>/`
18
+ - `internal/jobs/<name>/`
19
+ - You modify a module’s `routes.go` or its `RegisterRoutes` signature
20
+ - You see:
21
+ - “module not found” during mounting
22
+ - duplicate route registration panics
23
+
24
+ ## Quick Reference
25
+
26
+ - **Codegen entry:** `cmd/codegen/main.go`
27
+ - **Generated output:** `internal/api/routes_gen.go`
28
+ - **Run:**
29
+ - `go run cmd/codegen/main.go`
30
+ - or `make codegen`
31
+
32
+ ## Required Contract
33
+
34
+ ### RegisterRoutes signature (MUST match)
35
+
36
+ ```go
37
+ func RegisterRoutes(r *gin.RouterGroup, cfg *config.Config, db *gorm.DB) {
38
+ handler := NewHandler(cfg, db)
39
+ group := r.Group("/mymodule")
40
+ {
41
+ group.GET("/endpoint", handler.GetData)
42
+ }
43
+ }
44
+ ```
45
+
46
+ ### Registry mounting (don’t bypass)
47
+
48
+ - Use `internal/registry/` to mount **generated** `BuiltinModules`.
49
+ - Do **not** manually import/register modules in `router.go`.
50
+
51
+ ## Hard Rules (No Exceptions)
52
+
53
+ - **Never** hand-edit `internal/api/routes_gen.go`.
54
+ - **Never** “just wire it up quickly” by manually importing a module into `router.go`.
55
+ - **Never** re-apply `authMiddleware` inside module routes (parent group already has it).
56
+
57
+ ## Common Rationalizations (and Why They’re Wrong)
58
+
59
+ | Excuse | Reality |
60
+ |---|---|
61
+ | “It’s one module, manual import is faster.” | Creates duplicates / drift; next codegen run breaks or overwrites behavior. |
62
+ | “Codegen is optional.” | Architecture requires generated registry; skipping causes missing mounts. |
63
+ | “I can tweak routes_gen.go directly.” | Generated file is overwritten; manual edits are lost and can break builds. |
64
+
65
+ ## Red Flags — STOP
66
+
67
+ - You are about to edit `internal/api/routes_gen.go`
68
+ - You are about to import a module directly in `router.go`
69
+ - You changed `RegisterRoutes` signature
70
+
71
+ **All of these mean: run codegen and follow registry mounting.**
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: mo-dev-go-frontend-route
3
+ description: Use when adding, removing, or moving Vue modules under web/src/apps, web/src/system, web/src/widgets, or web/src/jobs where module routing is code-generated
4
+ ---
5
+
6
+ # Frontend Dynamic Routing (Codegen)
7
+
8
+ ## Overview
9
+
10
+ Frontend module routing is **compile-time generated**. Manually importing modules or skipping codegen creates missing registry entries and runtime module errors.
11
+
12
+ ## When to Use
13
+
14
+ - You add/remove/move a module under:
15
+ - `web/src/apps/<name>/`
16
+ - `web/src/system/<name>/`
17
+ - `web/src/widgets/<name>/`
18
+ - `web/src/jobs/<name>/`
19
+ - You change a module `index.ts` manifest
20
+ - You see errors like `Cannot find module '@/generated/modules'` or modules missing in UI
21
+
22
+ ## Quick Reference
23
+
24
+ - **Codegen script:** `web/scripts/codegen.js`
25
+ - **Generated output:** `web/src/generated/modules.ts`
26
+ - **Run:**
27
+ - `cd web && npm run codegen`
28
+ - or `make codegen`
29
+
30
+ ## Required Manifest Contract
31
+
32
+ ```ts
33
+ export const manifest: AppManifest = {
34
+ id: 'app-name',
35
+ title: { en: 'App Name', zh: '应用名称' },
36
+ icon: 'icon-name',
37
+ component: () => import('./App.vue')
38
+ }
39
+ ```
40
+
41
+ **Critical:** Use `title` (not `name`) for i18n labels.
42
+
43
+ ## Hard Rules (No Exceptions)
44
+
45
+ - **Never** hand-edit `web/src/generated/modules.ts`.
46
+ - **Never** manually import/register modules in window manager or control panel.
47
+ - **Never** skip codegen after adding/moving a module.
48
+
49
+ ## Common Rationalizations (and Why They’re Wrong)
50
+
51
+ | Excuse | Reality |
52
+ |---|---|
53
+ | “It’s faster to import manually.” | Registry stays stale; module won’t appear in app launcher. |
54
+ | “Codegen is optional.” | Generated registry is required for discovery and lazy-loading. |
55
+ | “I can tweak generated file.” | Overwritten on next codegen run; breaks consistency. |
56
+
57
+ ## Red Flags — STOP
58
+
59
+ - You are about to edit `web/src/generated/modules.ts`
60
+ - You are about to add manual imports for a module
61
+ - You are skipping `npm run codegen`
62
+
63
+ **All of these mean: run codegen and use the manifest discovery pattern.**
@@ -0,0 +1,249 @@
1
+ ---
2
+ name: mo-dev-go-module-api
3
+ description: Use when adding GET/PUT settings endpoints to existing Go modules with persistent JSON configuration
4
+ ---
5
+
6
+ # Adding Module Settings API
7
+
8
+ ## Overview
9
+
10
+ Add settings APIs to Go modules following the workspace JSON storage pattern with strict validation and response envelope.
11
+
12
+ ## When to Use
13
+
14
+ - Adding GET/PUT config endpoints to apps/system/widgets modules
15
+ - Module needs persistent user-specific settings
16
+ - Settings stored as JSON in workspace (not DB)
17
+
18
+ ## Core Pattern
19
+
20
+ **Endpoint convention:** GET/PUT `/api/<module>/config`
21
+ **Storage:** `workspace/<username>/.ft-webos/<module>.json`
22
+ **Response:** Always `{"success":bool,"data":any,"error":string}`
23
+
24
+ ## Quick Reference
25
+
26
+ | File | Purpose |
27
+ |------|---------|
28
+ | `settings.go` | Model + validation + defaults |
29
+ | `service.go` | GetSettings, UpdateSettings, getSettingsPath |
30
+ | `handler.go` | GetSettings, UpdateSettings handlers |
31
+ | `routes.go` | Register GET/PUT /config |
32
+ | `settings_test.go` | Unit tests |
33
+
34
+ ## Implementation Checklist
35
+
36
+ - [ ] **Response envelope enforced**: All handlers return `{"success":bool,"data":any,"error":string}`
37
+ - [ ] **Path validation**: Validate workspace path against root to prevent traversal
38
+ - [ ] **GET/PUT /config naming**: Use `/api/<module>/config` (not /settings)
39
+ - [ ] **JSON file storage**: `workspace/<username>/.ft-webos/<module>.json` (no DB)
40
+ - [ ] **Input validation**: Schema or field checks before processing
41
+ - [ ] **Error handling**: Log + HTTP status + error field populated
42
+
43
+ ## Settings Model Template
44
+
45
+ ```go
46
+ package mymodule
47
+
48
+ import "errors"
49
+
50
+ type MyModuleSettings struct {
51
+ Field1 bool `json:"field1"`
52
+ Field2 string `json:"field2"`
53
+ }
54
+
55
+ var AllowedField2Values = []string{"option1", "option2"}
56
+ var ErrInvalidField2 = errors.New("invalid field2 value")
57
+
58
+ func DefaultSettings() *MyModuleSettings {
59
+ return &MyModuleSettings{
60
+ Field1: false,
61
+ Field2: "option1",
62
+ }
63
+ }
64
+
65
+ func (s *MyModuleSettings) Validate() error {
66
+ if !contains(AllowedField2Values, s.Field2) {
67
+ return ErrInvalidField2
68
+ }
69
+ return nil
70
+ }
71
+
72
+ func contains(slice []string, item string) bool {
73
+ for _, v := range slice {
74
+ if v == item {
75
+ return true
76
+ }
77
+ }
78
+ return false
79
+ }
80
+ ```
81
+
82
+ ## Service Layer Template
83
+
84
+ ```go
85
+ func (s *Service) GetSettings(username string) (*MyModuleSettings, error) {
86
+ path := s.getSettingsPath(username)
87
+
88
+ // Validate path against workspace root
89
+ if !strings.HasPrefix(path, s.cfg.WorkspaceRoot) {
90
+ return nil, errors.New("invalid settings path")
91
+ }
92
+
93
+ data, err := os.ReadFile(path)
94
+ if os.IsNotExist(err) {
95
+ return DefaultSettings(), nil
96
+ }
97
+ if err != nil {
98
+ return nil, fmt.Errorf("read settings: %w", err)
99
+ }
100
+
101
+ var settings MyModuleSettings
102
+ if err := json.Unmarshal(data, &settings); err != nil {
103
+ return nil, fmt.Errorf("parse settings: %w", err)
104
+ }
105
+
106
+ return &settings, nil
107
+ }
108
+
109
+ func (s *Service) UpdateSettings(username string, settings *MyModuleSettings) error {
110
+ if err := settings.Validate(); err != nil {
111
+ return err
112
+ }
113
+
114
+ path := s.getSettingsPath(username)
115
+
116
+ // Validate path against workspace root
117
+ if !strings.HasPrefix(path, s.cfg.WorkspaceRoot) {
118
+ return errors.New("invalid settings path")
119
+ }
120
+
121
+ // Ensure directory exists
122
+ if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
123
+ return fmt.Errorf("create settings dir: %w", err)
124
+ }
125
+
126
+ data, err := json.MarshalIndent(settings, "", " ")
127
+ if err != nil {
128
+ return fmt.Errorf("marshal settings: %w", err)
129
+ }
130
+
131
+ if err := os.WriteFile(path, data, 0644); err != nil {
132
+ return fmt.Errorf("write settings: %w", err)
133
+ }
134
+
135
+ return nil
136
+ }
137
+
138
+ func (s *Service) getSettingsPath(username string) string {
139
+ return filepath.Join(s.cfg.WorkspaceRoot, username, ".ft-webos", "mymodule.json")
140
+ }
141
+ ```
142
+
143
+ ## Handler Template
144
+
145
+ ```go
146
+ func (h *Handler) GetSettings(c *gin.Context) {
147
+ userVal, exists := c.Get("user")
148
+ if !exists {
149
+ c.JSON(http.StatusUnauthorized, gin.H{
150
+ "success": false,
151
+ "data": nil,
152
+ "error": "unauthorized",
153
+ })
154
+ return
155
+ }
156
+
157
+ user := userVal.(models.User)
158
+ settings, err := h.service.GetSettings(user.Username)
159
+ if err != nil {
160
+ logger.Error("Failed to get settings", "error", err)
161
+ c.JSON(http.StatusInternalServerError, gin.H{
162
+ "success": false,
163
+ "data": nil,
164
+ "error": "failed to load settings",
165
+ })
166
+ return
167
+ }
168
+
169
+ c.JSON(http.StatusOK, gin.H{
170
+ "success": true,
171
+ "data": settings,
172
+ "error": "",
173
+ })
174
+ }
175
+
176
+ func (h *Handler) UpdateSettings(c *gin.Context) {
177
+ userVal, exists := c.Get("user")
178
+ if !exists {
179
+ c.JSON(http.StatusUnauthorized, gin.H{
180
+ "success": false,
181
+ "data": nil,
182
+ "error": "unauthorized",
183
+ })
184
+ return
185
+ }
186
+
187
+ var settings MyModuleSettings
188
+ if err := c.ShouldBindJSON(&settings); err != nil {
189
+ c.JSON(http.StatusBadRequest, gin.H{
190
+ "success": false,
191
+ "data": nil,
192
+ "error": "invalid request body",
193
+ })
194
+ return
195
+ }
196
+
197
+ user := userVal.(models.User)
198
+ if err := h.service.UpdateSettings(user.Username, &settings); err != nil {
199
+ logger.Error("Failed to update settings", "error", err)
200
+ c.JSON(http.StatusBadRequest, gin.H{
201
+ "success": false,
202
+ "data": nil,
203
+ "error": err.Error(),
204
+ })
205
+ return
206
+ }
207
+
208
+ c.JSON(http.StatusOK, gin.H{
209
+ "success": true,
210
+ "data": settings,
211
+ "error": "",
212
+ })
213
+ }
214
+ ```
215
+
216
+ ## Route Registration
217
+
218
+ ```go
219
+ func RegisterRoutes(r *gin.RouterGroup, cfg *config.Config, db *gorm.DB) {
220
+ handler := NewHandler(cfg, db)
221
+ group := r.Group("/mymodule")
222
+ {
223
+ group.GET("/config", handler.GetSettings)
224
+ group.PUT("/config", handler.UpdateSettings)
225
+ }
226
+ }
227
+ ```
228
+
229
+ ## Common Mistakes
230
+
231
+ | Mistake | Fix |
232
+ |---------|-----|
233
+ | Using POST instead of PUT | Use PUT for updates (idempotent) |
234
+ | Returning raw struct | Always wrap in `{"success":bool,"data":any,"error":string}` |
235
+ | Skipping path validation | Validate against workspace root to prevent traversal |
236
+ | Using DB for settings | Use JSON files in workspace/<username>/.ft-webos/ |
237
+ | Endpoint named /settings | Use /config for consistency |
238
+ | No input validation | Call `settings.Validate()` before saving |
239
+
240
+ ## Red Flags - STOP and Fix
241
+
242
+ - Response without success/data/error envelope
243
+ - File path not validated against workspace root
244
+ - Endpoint not named /config
245
+ - Settings stored in DB instead of JSON file
246
+ - No validation before UpdateSettings
247
+ - Swallowing errors without logging
248
+
249
+ **All of these mean: Fix before proceeding.**
@@ -69,8 +69,8 @@ const _listTaskFiles = async (taskDir) => {
69
69
  return files.map((f) => f.name);
70
70
  };
71
71
 
72
- /** 提示词模板:阅读后为所选 md 的相对路径 */
73
- const _promptForTask = (relativePath) => `执行任务:读取当前工作目录下 ${relativePath} 的正文(frontmatter 之后),按其中要求完成任务。`;
72
+ /** 提示词模板:阅读后为所选 md 的相对路径(统一模板) */
73
+ const _promptForTask = (relativePath) => `执行任务:读取当前工作目录下 ${relativePath} 的正文(frontmatter 之后),按其中要求完成任务,若开启了 Team 模式则考虑使用 Team 模式执行,最多创建的 Worker(不包括Team Leader)最多5个,自动评估和创建,执行完成后 Team Leader 回写 ${relativePath} 追加 Changes 记录。`;
74
74
 
75
75
 
76
76
  module.exports = async () => {
@@ -117,8 +117,11 @@ const _yamlFrontmatter = (attrs) => {
117
117
  return lines.join('\n') + '\n';
118
118
  };
119
119
 
120
- /** 生成针对某槽位 task 文件的提示词 */
121
- const _taskPromptForSlot = (filename) => `执行任务:读取当前工作目录下 .r2mo/task/${filename} 的正文(frontmatter 之后),按其中要求完成任务。`;
120
+ /** 生成针对某槽位 task 文件的提示词(统一模板) */
121
+ const _taskPromptForSlot = (filename) => {
122
+ const relativePath = `.r2mo/task/${filename}`;
123
+ return `执行任务:读取当前工作目录下 ${relativePath} 的正文(frontmatter 之后),按其中要求完成任务,若开启了 Team 模式则考虑使用 Team 模式执行,最多创建的 Worker(不包括Team Leader)最多5个,自动评估和创建,执行完成后 Team Leader 回写 ${relativePath} 追加 Changes 记录。`;
124
+ };
122
125
 
123
126
  module.exports = (options) => {
124
127
  try {