wogiflow 2.33.0 → 2.34.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.workflow/templates/partials/methodology-rules.hbs +3 -1
  2. package/lib/scheduled-mode.js +12 -15
  3. package/lib/skill-export-claude-plugin.js +41 -1
  4. package/lib/skill-portability.js +21 -3
  5. package/lib/workspace-channel-server.js +116 -3
  6. package/lib/workspace-channel-tracking.js +102 -1
  7. package/lib/workspace-dispatch-tracking.js +28 -0
  8. package/lib/workspace-messages.js +32 -4
  9. package/lib/workspace-subtask-state.js +215 -0
  10. package/lib/workspace.js +81 -0
  11. package/package.json +2 -2
  12. package/scripts/flow +17 -0
  13. package/scripts/flow-constants.js +3 -1
  14. package/scripts/flow-io.js +17 -0
  15. package/scripts/flow-paths.js +81 -0
  16. package/scripts/flow-schedule.js +23 -6
  17. package/scripts/flow-scheduled-runner.js +53 -8
  18. package/scripts/flow-standards-checker.js +37 -0
  19. package/scripts/flow-utils.js +2 -0
  20. package/scripts/hooks/adapters/claude-code.js +6 -2
  21. package/scripts/hooks/core/git-safety-gate.js +34 -15
  22. package/scripts/hooks/core/long-input-enforcement.js +49 -39
  23. package/scripts/hooks/core/overdue-dispatches.js +28 -6
  24. package/scripts/hooks/core/phase-gate.js +34 -5
  25. package/scripts/hooks/core/phase-read-gate.js +62 -10
  26. package/scripts/hooks/core/session-start-worker.js +52 -0
  27. package/scripts/hooks/core/stop-orchestrator.js +17 -2
  28. package/scripts/hooks/core/validation.js +8 -0
  29. package/scripts/hooks/core/worker-continuation-gate.js +487 -0
  30. package/scripts/hooks/core/workspace-stop-gates.js +21 -0
  31. package/scripts/hooks/core/workspace-stop-notify.js +174 -59
  32. package/scripts/hooks/entry/claude-code/post-tool-use.js +26 -0
  33. package/.claude/rules/README.md +0 -36
  34. package/.claude/rules/_internal/README.md +0 -64
  35. package/.claude/rules/_internal/document-structure.md +0 -77
  36. package/.claude/rules/_internal/dual-repo-management.md +0 -174
  37. package/.claude/rules/_internal/feature-refactoring-cleanup.md +0 -87
  38. package/.claude/rules/_internal/github-releases.md +0 -71
  39. package/.claude/rules/_internal/model-management.md +0 -35
  40. package/.claude/rules/_internal/self-maintenance.md +0 -87
  41. package/.claude/rules/_internal/worker-tool-first-turn.md +0 -82
  42. package/.claude/rules/alternative-execpolicy-toml-command-policy.md +0 -11
  43. package/.claude/rules/alternative-hand-edit-ready-json-to-register-orpha.md +0 -11
  44. package/.claude/rules/alternative-hook-args-exec-form.md +0 -6
  45. package/.claude/rules/alternative-permission-ruleset-per-phase.md +0 -11
  46. package/.claude/rules/alternative-short-name.md +0 -12
  47. package/.claude/rules/alternative-wogi-flow-as-mcp-client-oauth-manager.md +0 -11
  48. package/.claude/rules/architecture/component-reuse.md +0 -38
  49. package/.claude/rules/architecture/hook-three-layer.md +0 -68
  50. package/.claude/rules/code-style/naming-conventions.md +0 -107
  51. package/.claude/rules/dual-repo-architecture-2026-02-28.md +0 -18
  52. package/.claude/rules/github-release-workflow-2026-01-30.md +0 -16
  53. package/.claude/rules/operations/git-workflows.md +0 -92
  54. package/.claude/rules/operations/scratch-directory.md +0 -54
  55. package/.claude/skills/figma-analyzer/knowledge/learnings.md +0 -11
  56. package/.workflow/specs/architecture.md.template +0 -24
  57. package/.workflow/specs/stack.md.template +0 -33
  58. package/.workflow/specs/testing.md.template +0 -36
@@ -1,76 +1,191 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * Workspace worker-stopped notification (wf-6e31850e A-3 / extracted from stop.js).
4
+ * Workspace worker signal emission (epic-workspace-sustained-exec / S3, wf-d3ae1717;
5
+ * originally wf-6e31850e A-3 / wf-d3e67abe).
5
6
  *
6
- * Writes a structured `worker-stopped` message to the workspace message bus
7
- * so the manager's overdue-check can distinguish "graceful stop" from
8
- * "silent death" vs "task-complete". Original: wf-d3e67abe.
7
+ * Two distinct signals so the manager never mistakes a pause for a stop:
8
+ *
9
+ * notifyWorkerProgress() a HEARTBEAT, emitted when the continuation gate (S2)
10
+ * forces the worker to keep going. "Work ongoing," NOT a stop. Carries the
11
+ * current sub-task progress + git HEAD so the manager can refresh the
12
+ * dispatch deadline instead of polling git.
13
+ *
14
+ * notifyWorkerTerminal() — emitted ONLY on a genuine stop (after all gates
15
+ * decline to continue). Picks a precise terminal type:
16
+ * worker-awaiting-approval — in-progress task sitting in spec_review/exploring
17
+ * (waiting on the manager's GO — NOT done)
18
+ * worker-stopped — in-progress, active phase, but stopping anyway
19
+ * (legacy mid-work stop)
20
+ * worker-idle — nothing in progress and nothing queued
21
+ *
22
+ * The old code emitted `worker-stopped` UNCONDITIONALLY at the top of the Stop
23
+ * sequence — before the gates ran — so the manager saw "stopped mid-work" on
24
+ * every turn boundary, even when the worker was about to be forced to continue.
25
+ * That ordering bug is fixed: emission now happens at the decision point.
26
+ *
27
+ * All emission is best-effort and fail-open; never throws into the Stop path.
9
28
  */
10
29
 
11
- async function notifyWorkerStopped() {
12
- if (!process.env.WOGI_REPO_NAME || process.env.WOGI_REPO_NAME === 'manager') return;
30
+ const VALID_NAME = /^[a-zA-Z0-9_-]{1,64}$/;
31
+ const AWAITING_PHASES = new Set(['spec_review', 'exploring', 'routing', 'idle']);
32
+
33
+ function isWorker() {
34
+ return Boolean(process.env.WOGI_REPO_NAME &&
35
+ process.env.WOGI_REPO_NAME !== 'manager' &&
36
+ VALID_NAME.test(process.env.WOGI_REPO_NAME) &&
37
+ process.env.WOGI_WORKSPACE_ROOT);
38
+ }
39
+
40
+ function readState() {
41
+ const nodePath = require('node:path');
42
+ const { PATHS, safeJsonParse } = require('../../flow-utils');
43
+ const ready = safeJsonParse(nodePath.join(PATHS.state, 'ready.json'), {});
44
+ const phaseData = safeJsonParse(nodePath.join(PATHS.state, 'workflow-phase.json'), {});
45
+ const inProgressTask = (ready.inProgress || [])[0] || null;
46
+ const queued = (ready.ready || []).length;
47
+ let lastSha = null;
13
48
  try {
14
- const nodePath = require('node:path');
15
- const childProcess = require('node:child_process');
16
- const VALID_NAME = /^[a-zA-Z0-9_-]{1,64}$/;
17
- const repoName = process.env.WOGI_REPO_NAME;
18
- if (!VALID_NAME.test(repoName)) throw new Error('Invalid WOGI_REPO_NAME');
49
+ lastSha = require('node:child_process').execSync('git rev-parse --short HEAD 2>/dev/null || true', {
50
+ cwd: PATHS.root, encoding: 'utf-8', timeout: 2000
51
+ }).trim() || null;
52
+ } catch (_err) { /* non-critical */ }
53
+ return { inProgressTask, queued, phase: phaseData?.phase || null, lastSha };
54
+ }
19
55
 
20
- const workspaceRoot = process.env.WOGI_WORKSPACE_ROOT;
21
- if (!workspaceRoot) return;
56
+ function emit(type, fields) {
57
+ const nodePath = require('node:path');
58
+ const workspaceRoot = process.env.WOGI_WORKSPACE_ROOT;
59
+ const repoName = process.env.WOGI_REPO_NAME;
60
+ const libMessages = nodePath.resolve(__dirname, '..', '..', '..', 'lib', 'workspace-messages');
61
+ const { createMessage, saveMessage } = require(libMessages);
62
+ const msg = createMessage({
63
+ from: repoName,
64
+ to: 'manager',
65
+ type,
66
+ subject: fields.subject,
67
+ body: fields.body,
68
+ priority: fields.priority || 'medium',
69
+ actionRequired: Boolean(fields.actionRequired)
70
+ });
71
+ Object.assign(msg, fields.extra || {});
72
+ saveMessage(workspaceRoot, msg);
22
73
 
23
- const { PATHS, safeJsonParse } = require('../../flow-utils');
24
- const ready = safeJsonParse(nodePath.join(PATHS.state, 'ready.json'), {});
25
- const recentTask = (ready.recentlyCompleted || [])[0];
26
- const inProgressTask = (ready.inProgress || [])[0];
27
- const mostRecent = recentTask || inProgressTask;
74
+ // Real-time nudge to the manager port (best-effort) so it doesn't have to wait
75
+ // for its own next prompt to read the bus.
76
+ try {
77
+ const managerPort = process.env.WOGI_MANAGER_PORT;
78
+ if (managerPort) {
79
+ const http = require('node:http');
80
+ const payload = `[${type}] ${fields.subject}`;
81
+ const buf = Buffer.from(payload, 'utf-8');
82
+ const req = http.request({
83
+ hostname: '127.0.0.1', port: parseInt(managerPort, 10), path: '/', method: 'POST',
84
+ headers: { 'Content-Type': 'text/plain', 'Content-Length': buf.byteLength, 'X-Wogi-From': repoName }
85
+ });
86
+ req.on('error', () => {});
87
+ req.write(buf); req.end();
88
+ }
89
+ } catch (_err) { /* best effort */ }
90
+ }
28
91
 
29
- const hasInProgress = Boolean(inProgressTask);
30
- const state = hasInProgress ? 'mid-work' : 'idle';
31
- const taskInProgress = hasInProgress ? inProgressTask.id : null;
92
+ /**
93
+ * Heartbeat the continuation gate forced another turn. NOT a stop.
94
+ * @param {Object} info { taskId, remaining, total, attempt }
95
+ */
96
+ async function notifyWorkerProgress(info = {}) {
97
+ if (!isWorker()) return;
98
+ try {
99
+ const { lastSha } = readState();
100
+ emit('worker-progress', {
101
+ subject: `Worker ${process.env.WOGI_REPO_NAME} working on ${info.taskId || 'task'} (${info.remaining ?? '?'} sub-task(s) left)`,
102
+ body: [
103
+ `Worker "${process.env.WOGI_REPO_NAME}" is continuing (heartbeat).`,
104
+ info.taskId ? `Task: ${info.taskId}` : null,
105
+ (info.total != null) ? `Sub-tasks: ${(info.total - (info.remaining ?? 0))}/${info.total} done, ${info.remaining ?? '?'} remaining` : null,
106
+ info.attempt != null ? `Continuation: ${info.attempt}` : null,
107
+ lastSha ? `HEAD: ${lastSha}` : null
108
+ ].filter(Boolean).join('\n'),
109
+ priority: 'low',
110
+ actionRequired: false,
111
+ extra: {
112
+ reason: 'continuation',
113
+ state: 'in-progress',
114
+ taskId: info.taskId || null,
115
+ taskInProgress: info.taskId || null,
116
+ remaining: info.remaining ?? null,
117
+ total: info.total ?? null,
118
+ continuation: info.attempt ?? null,
119
+ lastSha,
120
+ heartbeatAt: new Date().toISOString()
121
+ }
122
+ });
123
+ } catch (err) {
124
+ if (process.env.DEBUG) console.error(`[Stop] worker-progress emit failed: ${err.message}`);
125
+ }
126
+ }
32
127
 
33
- let lastSha = null;
34
- try {
35
- lastSha = childProcess.execSync('git rev-parse --short HEAD 2>/dev/null || true', {
36
- cwd: PATHS.root,
37
- encoding: 'utf-8',
38
- timeout: 2000
39
- }).trim() || null;
40
- } catch (_err) { /* non-critical */ }
128
+ /**
129
+ * Terminal — emitted at a genuine stop. Picks the precise terminal type.
130
+ */
131
+ async function notifyWorkerTerminal() {
132
+ if (!isWorker()) return;
133
+ try {
134
+ const { inProgressTask, queued, phase, lastSha } = readState();
135
+ const taskId = inProgressTask?.id || null;
41
136
 
42
- try {
43
- const libMessages = nodePath.resolve(__dirname, '..', '..', '..', 'lib', 'workspace-messages');
44
- const { createMessage, saveMessage } = require(libMessages);
45
- const msg = createMessage({
46
- from: repoName,
47
- to: 'manager',
48
- type: 'worker-stopped',
49
- subject: hasInProgress
50
- ? `Worker stopped mid-work on ${taskInProgress}`
51
- : `Worker stopped (idle)`,
52
- body: [
53
- `Worker "${repoName}" is stopping.`,
54
- `State: ${state}`,
55
- taskInProgress ? `Task in progress: ${taskInProgress}` : null,
56
- mostRecent?.title ? `Most recent task: ${mostRecent.title}` : null,
57
- lastSha ? `Last commit: ${lastSha}` : null
58
- ].filter(Boolean).join('\n'),
59
- priority: hasInProgress ? 'high' : 'medium',
60
- actionRequired: hasInProgress
61
- });
62
- msg.taskId = taskInProgress;
63
- msg.reason = 'graceful';
64
- msg.state = state;
65
- msg.taskInProgress = taskInProgress;
66
- msg.lastSha = lastSha;
67
- saveMessage(workspaceRoot, msg);
68
- } catch (err) {
69
- if (process.env.DEBUG) console.error(`[Stop] Workspace message write failed: ${err.message}`);
137
+ let type, subject, priority, actionRequired, state;
138
+ if (!inProgressTask) {
139
+ type = 'worker-idle';
140
+ state = 'idle';
141
+ subject = `Worker ${process.env.WOGI_REPO_NAME} idle (${queued} queued)`;
142
+ priority = 'medium';
143
+ actionRequired = queued > 0; // queued-but-not-started is worth the manager's attention
144
+ } else if (AWAITING_PHASES.has(phase)) {
145
+ type = 'worker-awaiting-approval';
146
+ state = 'awaiting-approval';
147
+ subject = `Worker ${process.env.WOGI_REPO_NAME} awaiting approval on ${taskId} (phase: ${phase})`;
148
+ priority = 'high';
149
+ actionRequired = true;
150
+ } else {
151
+ type = 'worker-stopped';
152
+ state = 'mid-work';
153
+ subject = `Worker stopped mid-work on ${taskId}`;
154
+ priority = 'high';
155
+ actionRequired = true;
70
156
  }
157
+
158
+ emit(type, {
159
+ subject,
160
+ body: [
161
+ `Worker "${process.env.WOGI_REPO_NAME}" stopped.`,
162
+ `State: ${state}`,
163
+ taskId ? `Task in progress: ${taskId}` : `Queued: ${queued}`,
164
+ phase ? `Phase: ${phase}` : null,
165
+ lastSha ? `Last commit: ${lastSha}` : null
166
+ ].filter(Boolean).join('\n'),
167
+ priority,
168
+ actionRequired,
169
+ extra: {
170
+ reason: 'graceful',
171
+ state,
172
+ taskId,
173
+ taskInProgress: taskId,
174
+ phase,
175
+ lastSha
176
+ }
177
+ });
71
178
  } catch (err) {
72
- if (process.env.DEBUG) console.error(`[Stop] Workspace notification failed: ${err.message}`);
179
+ if (process.env.DEBUG) console.error(`[Stop] worker terminal emit failed: ${err.message}`);
73
180
  }
74
181
  }
75
182
 
76
- module.exports = { notifyWorkerStopped };
183
+ module.exports = {
184
+ notifyWorkerProgress,
185
+ notifyWorkerTerminal,
186
+ isWorker,
187
+ AWAITING_PHASES,
188
+ // Back-compat alias: the old single unconditional emitter. Now routes to the
189
+ // typed terminal notifier (callers that want a terminal stop signal).
190
+ notifyWorkerStopped: notifyWorkerTerminal
191
+ };
@@ -57,6 +57,32 @@ runHook('PostToolUse', async ({ parsedInput }) => {
57
57
  }
58
58
  }
59
59
 
60
+ // S1 (wf-e72350bf): mirror the worker's TodoWrite decomposition to the durable
61
+ // sub-task ledger so the continuation gate (S2) and restart-resume (S5) have a
62
+ // disk-readable "work remaining" signal. Worker-mode only by default (solo
63
+ // sessions opt in via workspace.subtaskLedger.alsoInSolo). Fail-open.
64
+ if (toolName === 'TodoWrite' && !toolFailed) {
65
+ try {
66
+ const { getConfig, PATHS, safeJsonParse } = require('../../../flow-utils');
67
+ const cfg = (() => { try { return getConfig()?.workspace?.subtaskLedger || {}; } catch (_err) { return {}; } })();
68
+ const ledgerEnabled = cfg.enabled !== false;
69
+ const isWorker = process.env.WOGI_WORKSPACE_ROOT &&
70
+ process.env.WOGI_REPO_NAME &&
71
+ process.env.WOGI_REPO_NAME !== 'manager';
72
+ if (ledgerEnabled && (isWorker || cfg.alsoInSolo === true)) {
73
+ const ready = safeJsonParse(require('node:path').join(PATHS.state, 'ready.json'), { inProgress: [] });
74
+ const taskId = ready.inProgress?.[0]?.id || null;
75
+ if (taskId) {
76
+ const subtaskState = require('../../../../lib/workspace-subtask-state');
77
+ const subs = subtaskState.subtasksFromTodos(toolInput);
78
+ if (subs.length > 0) subtaskState.write(taskId, subs);
79
+ }
80
+ }
81
+ } catch (err) {
82
+ if (process.env.DEBUG) console.error(`[post-tool-use] subtask-ledger mirror: ${err.message}`);
83
+ }
84
+ }
85
+
60
86
  // Track B B3 fix (2026-04-13): mark when templates are edited, so
61
87
  // session-end and the next /wogi-start can remind to run flow bridge sync.
62
88
  // Mechanical enforcement of self-maintenance.md §1.
@@ -1,36 +0,0 @@
1
- # Auto-Generated Rules
2
-
3
- This directory is auto-generated from `.workflow/state/decisions.md`.
4
-
5
- **DO NOT EDIT THESE FILES DIRECTLY.**
6
-
7
- Edit `decisions.md` instead, then run:
8
- ```bash
9
- node scripts/flow-rules-sync.js
10
- ```
11
-
12
- Or rules will auto-sync when decisions.md is updated.
13
-
14
- ## How It Works
15
-
16
- - Each section in decisions.md becomes a separate rule file
17
- - Rules are path-scoped based on section keywords (e.g., "component" rules only load for component files)
18
- - Claude Code automatically loads these rules for context-aware guidance
19
-
20
- ## Rule Types
21
-
22
- Rules have `alwaysApply` frontmatter that determines loading behavior:
23
-
24
- - **`alwaysApply: true`** - Always loaded (rules with: general, always, project, naming, coding, standard, convention, must, never, critical, security in title)
25
- - **`alwaysApply: false`** - Agent-requested: Claude decides whether to load based on description relevance to current task
26
-
27
- This saves tokens by not loading React rules when working on backend code, etc.
28
-
29
- ## Files
30
-
31
- - dual-repo-architecture-2026-02-28.md
32
- - github-release-workflow-2026-01-30.md
33
- - alternative-short-name.md
34
- - alternative-hand-edit-ready-json-to-register-orpha.md
35
-
36
- Last synced: 2026-04-24T06:42:44.238Z
@@ -1,64 +0,0 @@
1
- ---
2
- alwaysApply: false
3
- description: "Meta-documentation about how project rules are organized"
4
- ---
5
- # Project Rules
6
-
7
- This directory contains coding rules and patterns for this project, organized by category.
8
-
9
- ## Structure
10
-
11
- ```
12
- .claude/rules/
13
- ├── code-style/ # Naming conventions, formatting
14
- │ └── naming-conventions.md
15
- ├── security/ # Security patterns and practices
16
- │ └── security-patterns.md
17
- ├── architecture/ # Design decisions and patterns
18
- │ ├── component-reuse.md
19
- │ └── model-management.md
20
- └── README.md
21
- ```
22
-
23
- ## How Rules Work
24
-
25
- Rules are automatically loaded by Claude Code based on:
26
- - **alwaysApply: true** - Rule is always loaded
27
- - **alwaysApply: false** - Rule is loaded based on `globs` or `description` relevance
28
- - **globs** - File patterns that trigger rule loading
29
-
30
- ## Adding New Rules
31
-
32
- 1. Choose the appropriate category subdirectory
33
- 2. Create a `.md` file with frontmatter:
34
-
35
- ```yaml
36
- ---
37
- alwaysApply: false
38
- description: "Brief description for relevance matching"
39
- globs: src/**/*.ts # Optional: only load for these files
40
- ---
41
- ```
42
-
43
- 3. Write the rule content in markdown
44
-
45
- ## Categories
46
-
47
- | Category | Purpose |
48
- |----------|---------|
49
- | code-style | Naming conventions, formatting, file structure |
50
- | security | Security patterns, input validation, safe practices |
51
- | architecture | Design decisions, component patterns, system organization |
52
-
53
- ## Auto-Generation
54
-
55
- Some rules can be auto-generated from `.workflow/state/decisions.md`:
56
-
57
- ```bash
58
- node scripts/flow-rules-sync.js
59
- ```
60
-
61
- The sync script will route rules to appropriate category subdirectories.
62
-
63
- ---
64
- Last updated: 2026-01-12
@@ -1,77 +0,0 @@
1
- ---
2
- alwaysApply: false
3
- description: "All AI-context documents must use PIN markers for targeted context loading"
4
- globs: ".workflow/**/*.md"
5
- ---
6
-
7
- # Document Structure for AI Context
8
-
9
- All documents in `.workflow/` that are used as AI context MUST follow the PIN standard.
10
-
11
- ## Required Structure
12
-
13
- ### 1. Header with PIN List
14
- Every document starts with a comment listing all pins in the document:
15
- ```markdown
16
- <!-- PINS: pin1, pin2, pin3 -->
17
- ```
18
-
19
- ### 2. Section PIN Markers
20
- Each major section has a PIN marker comment:
21
- ```markdown
22
- ### Section Title
23
- <!-- PIN: section-specific-pin -->
24
- [Content]
25
- ```
26
-
27
- ### 3. PIN Naming Convention
28
- - Use kebab-case: `user-authentication`, not `userAuthentication`
29
- - Use semantic names: `error-handling`, not `eh`
30
- - Use compound names for specificity: `json-parse-safety`
31
-
32
- ## Why PINs Matter
33
-
34
- The PIN system enables:
35
- 1. **Targeted context loading**: Only load sections relevant to current task
36
- 2. **Cheaper model routing**: Haiku can fetch only relevant sections for Opus
37
- 3. **Change detection**: Hash sections independently for smart invalidation
38
- 4. **Cross-reference**: Link sections by PIN across documents
39
-
40
- ## Example Document
41
-
42
- ```markdown
43
- # Config Reference
44
-
45
- <!-- PINS: database, authentication, api-keys, environment -->
46
-
47
- ## Database Settings
48
- <!-- PIN: database -->
49
- | Setting | Default | Description |
50
- |---------|---------|-------------|
51
-
52
- ## Authentication
53
- <!-- PIN: authentication -->
54
- | Setting | Default | Description |
55
- |---------|---------|-------------|
56
- ```
57
-
58
- ## Parsing
59
-
60
- The PIN system automatically parses documents with:
61
- - `flow-section-index.js` - Generates section index with pins
62
- - `flow-section-resolver.js` - Resolves sections by PIN lookup
63
- - `getSectionsByPins(['auth', 'security'])` - Fetch only relevant sections
64
-
65
- ## Files That Must Have PINs
66
-
67
- | File | Required PINs |
68
- |------|---------------|
69
- | `decisions.md` | Per coding rule/pattern |
70
- | `app-map.md` | Per component/screen |
71
- | `product.md` | Per product section |
72
- | `stack.md` | Per technology |
73
-
74
- ## Validation
75
-
76
- Run `node scripts/flow-section-index.js --force` to regenerate the index.
77
- Check `.workflow/state/section-index.json` for indexed sections and their pins.
@@ -1,174 +0,0 @@
1
- ---
2
- alwaysApply: false
3
- description: "Dual-repo management rules for wogi-flow and wogiflow-cloud"
4
- globs: "package.json,lib/installer.js,lib/extension-points.js"
5
- ---
6
- # Dual-Repo Management: wogi-flow + wogiflow-cloud
7
-
8
- **Added**: 2026-02-28
9
- **Source**: User directive — formalize dual-repo architecture decisions
10
-
11
- ## Repository Ownership
12
-
13
- | Repo | Package | Visibility | Purpose |
14
- |------|---------|-----------|---------|
15
- | `wogi-flow` | `wogiflow` (npm) | Public (AGPL-3.0) | Free CLI, workflow engine, all local-only features |
16
- | `wogiflow-cloud` | `@wogiflow/teams` (client), `wogiflow-cloud-server` (server) | Private | Teams backend, client hooks, dashboard, portal logic |
17
- | `wogiflow-portal` | N/A (static site) | Public | wogi.ai — landing page, login, signup, knowledge base |
18
-
19
- ## Hard Rule: No Teams Code in the Free Repo
20
-
21
- Team-specific logic MUST NEVER appear in `wogi-flow`. This includes:
22
- - Auth/login UI beyond the thin `wogi login`/`wogi logout` adapter
23
- - Sync engines, presence, real-time features
24
- - Team CRUD, roles, permissions
25
- - Dashboard pages
26
- - Server-side API routes
27
-
28
- The free repo provides **extension points** (hooks in `lib/extension-points.js`) that the cloud client plugs into. The adapter pattern is:
29
- - `wogi-flow` exports hook interfaces and config schema
30
- - `@wogiflow/teams` imports those interfaces and adds team behavior
31
- - All team logic executes from `@wogiflow/teams`, never from `wogiflow` core
32
-
33
- ## Version Management
34
-
35
- ### Independent Versions, Mutual Awareness
36
-
37
- Each repo has its own semver version. They are NOT locked together.
38
-
39
- - `wogi-flow` → `package.json` version (currently 1.6.0)
40
- - `wogiflow-cloud` server → `packages/server/package.json` version (currently 0.1.0)
41
- - `@wogiflow/teams` client → `packages/client/package.json` version (currently 0.1.0)
42
-
43
- ### Cross-Repo Version File
44
-
45
- Each repo maintains a `.workflow/state/partner-versions.json` that records the last-known version of the other repo:
46
-
47
- ```json
48
- // In wogi-flow:
49
- {
50
- "self": { "package": "wogiflow", "version": "1.6.0" },
51
- "partners": {
52
- "wogiflow-cloud-server": { "version": "0.1.0", "checkedAt": "2026-02-28" },
53
- "wogiflow-teams-client": { "version": "0.1.0", "minCompatible": "1.5.0", "checkedAt": "2026-02-28" }
54
- }
55
- }
56
-
57
- // In wogiflow-cloud:
58
- {
59
- "self": { "package": "wogiflow-cloud", "version": "0.1.0" },
60
- "partners": {
61
- "wogiflow": { "version": "1.6.0", "checkedAt": "2026-02-28" }
62
- }
63
- }
64
- ```
65
-
66
- **Update rule**: When releasing either repo, update `partner-versions.json` in BOTH repos.
67
-
68
- ### Compatibility Contract
69
-
70
- The `@wogiflow/teams` client declares its minimum compatible `wogiflow` version via peerDependencies:
71
-
72
- ```json
73
- "peerDependencies": {
74
- "wogiflow": ">=1.5.0"
75
- }
76
- ```
77
-
78
- **When to bump the minimum**:
79
- - When the free repo removes or renames an exported function that the client uses
80
- - When the free repo changes the shape of config.json, ready.json, or other state files
81
- - When the free repo changes hook interfaces (entry point signatures, event payloads)
82
-
83
- **When NOT to bump**:
84
- - New features added to the free repo (additive changes are always compatible)
85
- - Internal refactoring that doesn't change exports
86
-
87
- ## Interface Contract (Public API Surface)
88
-
89
- The cloud client depends on these interfaces from `wogi-flow`. Changes to any of these require updating the client:
90
-
91
- ### Exported Functions (from scripts/)
92
- - `flow-utils.js`: `getConfig()`, `safeJsonParse()`, `writeJson()`, `generateTaskId()`, `validateTaskId()`, `PATHS`, `getReadyData()`, `saveReadyData()`
93
- - `flow-session-state.js`: `trackTaskStart()`, `trackBypassAttempt()`
94
- - `flow-memory-blocks.js`: `setCurrentTask()`
95
-
96
- ### Hook Interfaces (entry point contracts)
97
- - `PreToolUse` hooks receive: `{ tool, toolInput }` via stdin JSON
98
- - `PostToolUse` hooks receive: `{ tool, toolInput, toolResult }` via stdin JSON
99
- - `TaskCompleted` hooks receive: `{ taskId }` via stdin JSON
100
- - `SessionStart`/`SessionEnd` hooks receive: `{}` via stdin JSON
101
-
102
- ### State File Formats
103
- - `ready.json`: `{ inProgress: [], ready: [], blocked: [], recentlyCompleted: [], backlog: [] }`
104
- - `config.json`: Schema documented in `config.schema.json`
105
- - `decisions.md`: Markdown with `## Section` / `### Rule` structure
106
- - `session-state.json`: `{ taskId, status, lastBriefingAt, ... }`
107
-
108
- ### Config Keys Used by Cloud
109
- - `hooks.rules.*` — all hook toggle keys
110
- - `enforcement.*` — strict mode, task gating
111
- - `semanticMatching.*` — reuse detection thresholds
112
-
113
- **When modifying any of the above**: Check wogiflow-cloud for consumers BEFORE releasing.
114
-
115
- ## Change Propagation Rules
116
-
117
- ### OSS Change → Does Cloud Need Updating?
118
-
119
- | Change Type | Cloud Impact | Action Required |
120
- |-------------|-------------|-----------------|
121
- | New feature (additive) | None | No action needed |
122
- | Bug fix (internal) | None | No action needed |
123
- | Exported function renamed/removed | BREAKING | Update client, bump peerDep minimum |
124
- | State file format changed | BREAKING | Update client parsers |
125
- | Hook interface changed | BREAKING | Update client hooks |
126
- | Config key renamed/removed | BREAKING | Update client config readers |
127
- | New config key added | None (additive) | Client can optionally use it |
128
-
129
- ### Cloud Change → Does OSS Need Updating?
130
-
131
- | Change Type | OSS Impact | Action Required |
132
- |-------------|-----------|-----------------|
133
- | New server feature | None | No action needed |
134
- | New client hook | None | No action needed |
135
- | Client needs new OSS export | REQUIRES | Add export to OSS, release OSS first |
136
- | Dashboard changes | None | Entirely separate |
137
-
138
- ### Release Order
139
-
140
- 1. **OSS first**: If the cloud needs a new OSS feature, release OSS first
141
- 2. **Cloud follows**: Cloud releases independently, referencing the OSS version in peerDeps
142
- 3. **Never**: Release cloud with a dependency on an unreleased OSS version
143
-
144
- ```
145
- OSS v1.7.0 (adds new export)
146
-
147
- Cloud client v0.2.0 (uses new export, peerDep bumped to >=1.7.0)
148
-
149
- Cloud server v0.2.0 (may or may not change)
150
- ```
151
-
152
- ## Web Properties
153
-
154
- | Property | Repo | Purpose |
155
- |----------|------|---------|
156
- | wogi.ai (main site) | `wogiflow-portal` | Landing, login, signup, knowledge base |
157
- | Dashboard (admin UI) | `wogiflow-cloud/packages/dashboard/` | Team admin — served by cloud server |
158
-
159
- The portal is a separate deployment from the dashboard. The portal is public-facing (marketing + auth). The dashboard is authenticated (team management).
160
-
161
- ## Verification Checklist (Before Any Release)
162
-
163
- ### Releasing wogi-flow (OSS):
164
- 1. Check `partner-versions.json` — is cloud version current?
165
- 2. If any exported function/interface changed: grep wogiflow-cloud for consumers
166
- 3. Run `node --check` on all modified scripts
167
- 4. Follow GitHub Release Workflow (decisions.md)
168
- 5. After release: update `partner-versions.json` in wogiflow-cloud
169
-
170
- ### Releasing wogiflow-cloud:
171
- 1. Check `partner-versions.json` — is OSS version current?
172
- 2. Verify `peerDependencies.wogiflow` range includes current OSS version
173
- 3. If client needs new OSS export: release OSS first
174
- 4. After release: update `partner-versions.json` in wogi-flow