wogiflow 2.4.2 â 2.4.4
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/.claude/commands/wogi-start.md +124 -0
- package/.claude/docs/claude-code-compatibility.md +51 -0
- package/.claude/docs/explore-agents.md +11 -0
- package/.claude/settings.json +12 -1
- package/.workflow/models/registry.json +1 -1
- package/bin/flow +11 -1
- package/lib/workspace-contracts.js +599 -0
- package/lib/workspace-intelligence.js +600 -0
- package/lib/workspace-messages.js +441 -0
- package/lib/workspace-routing.js +485 -0
- package/lib/workspace-sync.js +339 -0
- package/lib/workspace.js +1073 -0
- package/package.json +4 -4
- package/scripts/MEMORY-ARCHITECTURE.md +1 -1
- package/scripts/base-workflow-step.js +136 -0
- package/scripts/flow-adaptive-learning.js +8 -9
- package/scripts/flow-aggregate.js +11 -6
- package/scripts/flow-api-index.js +4 -6
- package/scripts/flow-assumption-detector.js +0 -2
- package/scripts/flow-audit.js +15 -2
- package/scripts/flow-auto-context.js +8 -12
- package/scripts/flow-auto-learn.js +49 -49
- package/scripts/flow-background.js +5 -6
- package/scripts/flow-bridge-state.js +8 -10
- package/scripts/flow-bulk-loop.js +1 -3
- package/scripts/flow-bulk-orchestrator.js +1 -3
- package/scripts/flow-cascade-completion.js +0 -2
- package/scripts/flow-cascade.js +4 -4
- package/scripts/flow-checkpoint.js +10 -13
- package/scripts/flow-code-intelligence.js +10 -12
- package/scripts/flow-community-sync.js +4 -4
- package/scripts/flow-community.js +12 -20
- package/scripts/flow-config-defaults.js +28 -2
- package/scripts/flow-config-interactive.js +9 -5
- package/scripts/flow-config-loader.js +49 -92
- package/scripts/flow-config-substitution.js +0 -2
- package/scripts/flow-context-estimator.js +4 -4
- package/scripts/flow-context-init.js +10 -12
- package/scripts/flow-context-manager.js +0 -2
- package/scripts/flow-context-scoring.js +2 -2
- package/scripts/flow-contract-scan.js +6 -9
- package/scripts/flow-correct.js +29 -27
- package/scripts/flow-correction-detector.js +5 -1
- package/scripts/flow-damage-control.js +47 -54
- package/scripts/flow-decisions-merge.js +4 -14
- package/scripts/flow-diff.js +5 -8
- package/scripts/flow-done-gates.js +786 -0
- package/scripts/flow-done-report.js +123 -0
- package/scripts/flow-done.js +71 -717
- package/scripts/flow-entropy-monitor.js +1 -3
- package/scripts/flow-eval-calibration.js +257 -0
- package/scripts/flow-eval-judge.js +10 -1
- package/scripts/flow-eval.js +14 -5
- package/scripts/flow-extraction-review.js +1 -0
- package/scripts/flow-failure-categories.js +0 -2
- package/scripts/flow-figma-confirm.js +5 -9
- package/scripts/flow-figma-generate.js +8 -10
- package/scripts/flow-figma-index.js +8 -10
- package/scripts/flow-figma-match.js +3 -5
- package/scripts/flow-figma-mcp-server.js +2 -4
- package/scripts/flow-figma-orchestrator.js +2 -3
- package/scripts/flow-figma-registry.js +2 -3
- package/scripts/flow-framework-resolver.js +0 -2
- package/scripts/flow-function-index.js +4 -6
- package/scripts/flow-gate-confidence.js +2 -2
- package/scripts/flow-gitignore.js +0 -2
- package/scripts/flow-guided-edit.js +5 -6
- package/scripts/flow-health.js +5 -6
- package/scripts/flow-hook-errors.js +6 -0
- package/scripts/flow-hook-status.js +263 -0
- package/scripts/flow-hooks.js +17 -29
- package/scripts/flow-http-client.js +9 -8
- package/scripts/flow-hybrid-interactive.js +7 -12
- package/scripts/flow-hybrid-test.js +12 -13
- package/scripts/flow-instruction-richness.js +1 -1
- package/scripts/flow-io.js +21 -4
- package/scripts/flow-knowledge-router.js +9 -3
- package/scripts/flow-learning-orchestrator.js +318 -13
- package/scripts/flow-links.js +5 -7
- package/scripts/flow-long-input-association.js +275 -0
- package/scripts/flow-long-input-chunking.js +1 -0
- package/scripts/flow-long-input-cli.js +0 -2
- package/scripts/flow-long-input-complexity.js +0 -2
- package/scripts/flow-long-input-constants.js +0 -2
- package/scripts/flow-long-input-contradictions.js +351 -0
- package/scripts/flow-long-input-detection.js +0 -2
- package/scripts/flow-long-input-passes.js +885 -0
- package/scripts/flow-long-input-stories.js +1 -1
- package/scripts/flow-long-input-voice.js +0 -2
- package/scripts/flow-long-input.js +425 -3005
- package/scripts/flow-loop-retry-learning.js +2 -3
- package/scripts/flow-lsp.js +3 -3
- package/scripts/flow-mcp-docs.js +3 -4
- package/scripts/flow-memory-db.js +6 -8
- package/scripts/flow-memory-sync.js +18 -11
- package/scripts/flow-metrics.js +1 -2
- package/scripts/flow-model-adapter.js +2 -3
- package/scripts/flow-model-config.js +72 -104
- package/scripts/flow-model-router.js +2 -2
- package/scripts/flow-model-types.js +0 -2
- package/scripts/flow-multi-approach.js +5 -6
- package/scripts/flow-orchestrate-context.js +3 -7
- package/scripts/flow-orchestrate-rollback.js +3 -8
- package/scripts/flow-orchestrate-state.js +8 -14
- package/scripts/flow-orchestrate-templates.js +2 -6
- package/scripts/flow-orchestrate-validator.js +5 -9
- package/scripts/flow-orchestrate.js +126 -103
- package/scripts/flow-output.js +0 -2
- package/scripts/flow-parallel.js +1 -1
- package/scripts/flow-paths.js +23 -2
- package/scripts/flow-pattern-enforcer.js +30 -28
- package/scripts/flow-pattern-extractor.js +3 -4
- package/scripts/flow-pending.js +0 -2
- package/scripts/flow-permissions.js +2 -3
- package/scripts/flow-plugin-registry.js +10 -12
- package/scripts/flow-prd-manager.js +1 -1
- package/scripts/flow-progress.js +7 -9
- package/scripts/flow-prompt-composer.js +3 -3
- package/scripts/flow-prompt-template.js +2 -2
- package/scripts/flow-providers.js +7 -4
- package/scripts/flow-registry-manager.js +7 -12
- package/scripts/flow-regression.js +9 -11
- package/scripts/flow-roadmap.js +2 -2
- package/scripts/flow-run-trace.js +16 -15
- package/scripts/flow-safety.js +2 -5
- package/scripts/flow-scanner-base.js +5 -7
- package/scripts/flow-scenario-engine.js +1 -5
- package/scripts/flow-security.js +29 -0
- package/scripts/flow-session-end.js +32 -41
- package/scripts/flow-session-learning.js +53 -49
- package/scripts/flow-setup-hooks.js +2 -3
- package/scripts/flow-skill-create.js +7 -12
- package/scripts/flow-skill-generator.js +12 -16
- package/scripts/flow-skill-learn.js +17 -8
- package/scripts/flow-skill-matcher.js +1 -2
- package/scripts/flow-spec-generator.js +2 -4
- package/scripts/flow-stack-wizard.js +5 -7
- package/scripts/flow-standards-learner.js +35 -16
- package/scripts/flow-start.js +2 -0
- package/scripts/flow-stats-collector.js +2 -2
- package/scripts/flow-status.js +10 -10
- package/scripts/flow-statusline-setup.js +2 -2
- package/scripts/flow-step-changelog.js +2 -3
- package/scripts/flow-step-comments.js +66 -81
- package/scripts/flow-step-complexity.js +50 -70
- package/scripts/flow-step-coverage.js +3 -5
- package/scripts/flow-step-knowledge.js +2 -3
- package/scripts/flow-step-pr-tests.js +64 -74
- package/scripts/flow-step-regression.js +3 -5
- package/scripts/flow-step-review.js +86 -103
- package/scripts/flow-step-security.js +111 -121
- package/scripts/flow-step-silent-failures.js +56 -83
- package/scripts/flow-step-simplifier.js +52 -70
- package/scripts/flow-story.js +4 -7
- package/scripts/flow-strict-adherence.js +3 -4
- package/scripts/flow-task-checkpoint.js +36 -5
- package/scripts/flow-task-enforcer.js +2 -24
- package/scripts/flow-tech-debt.js +1 -1
- package/scripts/flow-template-extractor.js +1 -0
- package/scripts/flow-templates.js +11 -13
- package/scripts/flow-test-api.js +9 -13
- package/scripts/flow-test-discovery.js +1 -1
- package/scripts/flow-test-generate.js +5 -9
- package/scripts/flow-test-integrity.js +3 -7
- package/scripts/flow-test-ui.js +5 -9
- package/scripts/flow-testing-deps.js +1 -3
- package/scripts/flow-tiered-learning.js +4 -4
- package/scripts/flow-todowrite-sync.js +1 -1
- package/scripts/flow-tokens.js +0 -2
- package/scripts/flow-verification-profile.js +6 -10
- package/scripts/flow-verify.js +12 -16
- package/scripts/flow-version-check.js +4 -12
- package/scripts/flow-webmcp-generator.js +3 -5
- package/scripts/flow-workflow-steps.js +0 -2
- package/scripts/flow-workflow.js +9 -11
- package/scripts/hooks/adapters/claude-code.js +31 -0
- package/scripts/hooks/core/config-change.js +1 -0
- package/scripts/hooks/core/extension-registry.js +0 -2
- package/scripts/hooks/core/instructions-loaded.js +1 -1
- package/scripts/hooks/core/observation-capture.js +5 -5
- package/scripts/hooks/core/phase-gate.js +5 -0
- package/scripts/hooks/core/post-compact.js +1 -12
- package/scripts/hooks/core/research-gate.js +2 -12
- package/scripts/hooks/core/routing-gate.js +6 -0
- package/scripts/hooks/core/task-completed.js +12 -0
- package/scripts/hooks/core/task-created.js +83 -0
- package/scripts/hooks/core/worktree-lifecycle.js +1 -1
- package/scripts/hooks/entry/claude-code/config-change.js +6 -29
- package/scripts/hooks/entry/claude-code/instructions-loaded.js +5 -30
- package/scripts/hooks/entry/claude-code/post-compact.js +4 -31
- package/scripts/hooks/entry/claude-code/post-tool-use.js +121 -172
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +260 -361
- package/scripts/hooks/entry/claude-code/session-end.js +4 -28
- package/scripts/hooks/entry/claude-code/session-start.js +205 -243
- package/scripts/hooks/entry/claude-code/setup.js +8 -49
- package/scripts/hooks/entry/claude-code/stop.js +40 -72
- package/scripts/hooks/entry/claude-code/task-completed.js +4 -28
- package/scripts/hooks/entry/claude-code/task-created.js +15 -0
- package/scripts/hooks/entry/claude-code/user-prompt-submit.js +113 -195
- package/scripts/hooks/entry/claude-code/worktree-create.js +6 -25
- package/scripts/hooks/entry/claude-code/worktree-remove.js +6 -25
- package/scripts/hooks/entry/shared/hook-runner.js +99 -0
- package/scripts/hooks/entry/shared/read-stdin.js +0 -2
- package/scripts/postinstall.js +2 -0
- package/scripts/registries/api-registry.js +0 -2
- package/scripts/registries/component-registry.js +5 -9
- package/scripts/registries/contract-scanner.js +2 -9
- package/scripts/registries/function-registry.js +0 -2
- package/scripts/registries/schema-registry.js +14 -18
- package/scripts/registries/service-registry.js +23 -27
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Workspace â Sync & Lifecycle Management
|
|
5
|
+
*
|
|
6
|
+
* Story 6 (wf-a3a13d95): Re-reads member repo state files, updates manifest,
|
|
7
|
+
* detects changes, and provides unified workspace status.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('node:fs');
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
|
|
13
|
+
const { readMemberMetadata, extractCapabilities, extractEndpoints, detectStack, generateManifest, WORKSPACE_CONFIG_FILE, WORKSPACE_DIR } = require('./workspace');
|
|
14
|
+
const { buildIntegrationMap, detectTypeDrift } = require('./workspace-contracts');
|
|
15
|
+
const { getUnreadMessages, formatMessagesForDisplay } = require('./workspace-messages');
|
|
16
|
+
|
|
17
|
+
// ============================================================
|
|
18
|
+
// Workspace Sync (Criterion 1)
|
|
19
|
+
// ============================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Re-read all member repo state files and update the workspace manifest.
|
|
23
|
+
* @param {string} workspaceRoot
|
|
24
|
+
* @param {Object} [options] â { silent: boolean }
|
|
25
|
+
* @returns {Object} sync result
|
|
26
|
+
*/
|
|
27
|
+
function syncWorkspace(workspaceRoot, options = {}) {
|
|
28
|
+
const { silent = false } = options;
|
|
29
|
+
const configPath = path.join(workspaceRoot, WORKSPACE_CONFIG_FILE);
|
|
30
|
+
|
|
31
|
+
if (!fs.existsSync(configPath)) {
|
|
32
|
+
throw new Error('No wogi-workspace.json found. Run `flow workspace init` first.');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
36
|
+
const result = {
|
|
37
|
+
success: true,
|
|
38
|
+
membersUpdated: 0,
|
|
39
|
+
changes: [],
|
|
40
|
+
warnings: []
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Read old manifest for diff
|
|
44
|
+
const manifestPath = path.join(workspaceRoot, WORKSPACE_DIR, 'state', 'workspace-manifest.json');
|
|
45
|
+
let oldManifest = null;
|
|
46
|
+
try {
|
|
47
|
+
if (fs.existsSync(manifestPath)) {
|
|
48
|
+
oldManifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
49
|
+
}
|
|
50
|
+
} catch (_err) {
|
|
51
|
+
// Will regenerate from scratch
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Re-read each member
|
|
55
|
+
const members = [];
|
|
56
|
+
for (const [name, memberConfig] of Object.entries(config.members)) {
|
|
57
|
+
const memberPath = path.resolve(workspaceRoot, memberConfig.path);
|
|
58
|
+
const workflowPath = path.join(memberPath, '.workflow');
|
|
59
|
+
|
|
60
|
+
if (!fs.existsSync(workflowPath)) {
|
|
61
|
+
result.warnings.push(`Member '${name}' has no .workflow/ directory â skipping`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const metadata = readMemberMetadata(workflowPath);
|
|
66
|
+
const stack = detectStack(metadata, memberPath);
|
|
67
|
+
const capabilities = extractCapabilities(metadata);
|
|
68
|
+
const endpoints = extractEndpoints(metadata);
|
|
69
|
+
const role = memberConfig.role || 'standalone';
|
|
70
|
+
|
|
71
|
+
members.push({ name, path: memberPath, workflowPath, metadata, stack, capabilities, endpoints, role });
|
|
72
|
+
|
|
73
|
+
// Detect changes vs old manifest
|
|
74
|
+
if (oldManifest && oldManifest.members[name]) {
|
|
75
|
+
const oldMember = oldManifest.members[name];
|
|
76
|
+
const oldProvides = new Set(oldMember.provides || []);
|
|
77
|
+
const oldConsumes = new Set(oldMember.consumes || []);
|
|
78
|
+
|
|
79
|
+
const newProvides = endpoints.provides.filter(ep => !oldProvides.has(ep));
|
|
80
|
+
const removedProvides = [...oldProvides].filter(ep => !endpoints.provides.includes(ep));
|
|
81
|
+
const newConsumes = endpoints.consumes.filter(ep => !oldConsumes.has(ep));
|
|
82
|
+
const removedConsumes = [...oldConsumes].filter(ep => !endpoints.consumes.includes(ep));
|
|
83
|
+
|
|
84
|
+
if (newProvides.length > 0) result.changes.push({ member: name, type: 'new-provides', endpoints: newProvides });
|
|
85
|
+
if (removedProvides.length > 0) result.changes.push({ member: name, type: 'removed-provides', endpoints: removedProvides });
|
|
86
|
+
if (newConsumes.length > 0) result.changes.push({ member: name, type: 'new-consumes', endpoints: newConsumes });
|
|
87
|
+
if (removedConsumes.length > 0) result.changes.push({ member: name, type: 'removed-consumes', endpoints: removedConsumes });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
result.membersUpdated++;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Generate new manifest
|
|
94
|
+
const newManifest = generateManifest(config.name, members);
|
|
95
|
+
|
|
96
|
+
// Detect type drift
|
|
97
|
+
const memberMetadata = {};
|
|
98
|
+
for (const m of members) memberMetadata[m.name] = m.metadata;
|
|
99
|
+
const drifts = detectTypeDrift(newManifest, memberMetadata);
|
|
100
|
+
if (drifts.length > 0) {
|
|
101
|
+
newManifest.integrations.typeDrift = drifts;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Write updated manifest
|
|
105
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
106
|
+
fs.writeFileSync(manifestPath, JSON.stringify(newManifest, null, 2));
|
|
107
|
+
|
|
108
|
+
// Write updated integration map
|
|
109
|
+
const integrationMap = buildIntegrationMap(newManifest);
|
|
110
|
+
const mapLines = ['# Integration Map\n', `Generated: ${integrationMap.generatedAt}`, `Match rate: ${integrationMap.stats.matchRate}%\n`];
|
|
111
|
+
|
|
112
|
+
if (integrationMap.matched.length > 0) {
|
|
113
|
+
mapLines.push('## Matched Endpoints\n');
|
|
114
|
+
mapLines.push('| Endpoint | Provider(s) | Consumer(s) | Score |');
|
|
115
|
+
mapLines.push('|----------|-------------|-------------|-------|');
|
|
116
|
+
for (const m of integrationMap.matched) {
|
|
117
|
+
mapLines.push(`| \`${m.endpoint}\` | ${m.providers.join(', ')} | ${m.consumers.join(', ')} | ${(m.matchScore * 100).toFixed(0)}% |`);
|
|
118
|
+
}
|
|
119
|
+
mapLines.push('');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (integrationMap.orphanedConsumers.length > 0) {
|
|
123
|
+
mapLines.push('## Orphaned Consumers\n');
|
|
124
|
+
for (const o of integrationMap.orphanedConsumers) {
|
|
125
|
+
mapLines.push(`- \`${o.endpoint}\` â consumed by: ${o.consumers.join(', ')}`);
|
|
126
|
+
}
|
|
127
|
+
mapLines.push('');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fs.writeFileSync(
|
|
131
|
+
path.join(workspaceRoot, WORKSPACE_DIR, 'state', 'integration-map.md'),
|
|
132
|
+
mapLines.join('\n')
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (!silent && result.changes.length > 0) {
|
|
136
|
+
console.log(`\nââ Changes detected ââââââââââââââââââ\n`);
|
|
137
|
+
for (const change of result.changes) {
|
|
138
|
+
const icon = change.type.includes('new') ? 'â' : 'â';
|
|
139
|
+
console.log(` ${icon} ${change.member}: ${change.type} â ${change.endpoints.join(', ')}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ============================================================
|
|
147
|
+
// Workspace Status (Criterion 5)
|
|
148
|
+
// ============================================================
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Generate a unified workspace status report
|
|
152
|
+
* @param {string} workspaceRoot
|
|
153
|
+
* @returns {string} formatted status report
|
|
154
|
+
*/
|
|
155
|
+
function getWorkspaceStatus(workspaceRoot) {
|
|
156
|
+
const configPath = path.join(workspaceRoot, WORKSPACE_CONFIG_FILE);
|
|
157
|
+
if (!fs.existsSync(configPath)) return 'No workspace found. Run `flow workspace init` first.';
|
|
158
|
+
|
|
159
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
160
|
+
const manifestPath = path.join(workspaceRoot, WORKSPACE_DIR, 'state', 'workspace-manifest.json');
|
|
161
|
+
const manifest = fs.existsSync(manifestPath)
|
|
162
|
+
? JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
|
|
163
|
+
: null;
|
|
164
|
+
|
|
165
|
+
const lines = [];
|
|
166
|
+
lines.push(`đī¸ Wogi Workspace: ${config.name}`);
|
|
167
|
+
lines.push('â'.repeat(40));
|
|
168
|
+
lines.push('');
|
|
169
|
+
|
|
170
|
+
// Members
|
|
171
|
+
lines.push('Members:');
|
|
172
|
+
for (const [name, memberConfig] of Object.entries(config.members)) {
|
|
173
|
+
const memberPath = path.resolve(workspaceRoot, memberConfig.path);
|
|
174
|
+
const readyPath = path.join(memberPath, '.workflow', 'state', 'ready.json');
|
|
175
|
+
|
|
176
|
+
let taskSummary = 'no .workflow/';
|
|
177
|
+
try {
|
|
178
|
+
if (fs.existsSync(readyPath)) {
|
|
179
|
+
const ready = JSON.parse(fs.readFileSync(readyPath, 'utf-8'));
|
|
180
|
+
const inProgress = (ready.inProgress || []).length;
|
|
181
|
+
const readyCount = (ready.ready || []).length;
|
|
182
|
+
taskSummary = `${inProgress} in progress, ${readyCount} ready`;
|
|
183
|
+
}
|
|
184
|
+
} catch (_err) {
|
|
185
|
+
taskSummary = 'error reading';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const memberManifest = manifest?.members?.[name];
|
|
189
|
+
const stack = memberManifest ? `${memberManifest.stack.language}/${memberManifest.stack.framework}` : 'unknown';
|
|
190
|
+
|
|
191
|
+
lines.push(` đĻ ${name} (${stack}) â ${taskSummary}`);
|
|
192
|
+
}
|
|
193
|
+
lines.push('');
|
|
194
|
+
|
|
195
|
+
// Integration summary
|
|
196
|
+
if (manifest) {
|
|
197
|
+
const matched = manifest.integrations.matched?.length ?? 0;
|
|
198
|
+
const orphanedC = manifest.integrations.orphanedConsumers?.length ?? 0;
|
|
199
|
+
const orphanedP = manifest.integrations.orphanedProviders?.length ?? 0;
|
|
200
|
+
const drifts = manifest.integrations.typeDrift?.length ?? 0;
|
|
201
|
+
|
|
202
|
+
lines.push('Integrations:');
|
|
203
|
+
lines.push(` đ ${matched} matched endpoints`);
|
|
204
|
+
if (orphanedC > 0) lines.push(` â ī¸ ${orphanedC} orphaned consumer${orphanedC !== 1 ? 's' : ''}`);
|
|
205
|
+
if (orphanedP > 0) lines.push(` âšī¸ ${orphanedP} endpoint${orphanedP !== 1 ? 's' : ''} without consumers`);
|
|
206
|
+
if (drifts > 0) lines.push(` â ī¸ ${drifts} type drift${drifts !== 1 ? 's' : ''} detected`);
|
|
207
|
+
lines.push('');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Messages
|
|
211
|
+
const unread = getUnreadMessages(workspaceRoot, 'all');
|
|
212
|
+
if (unread.length > 0) {
|
|
213
|
+
lines.push(`Messages (${unread.length} unread):`);
|
|
214
|
+
lines.push(formatMessagesForDisplay(unread, 5));
|
|
215
|
+
lines.push('');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Contracts
|
|
219
|
+
const contractsDir = path.join(workspaceRoot, WORKSPACE_DIR, 'contracts');
|
|
220
|
+
if (fs.existsSync(contractsDir)) {
|
|
221
|
+
const contracts = fs.readdirSync(contractsDir).filter(f => !f.startsWith('.'));
|
|
222
|
+
if (contracts.length > 0) {
|
|
223
|
+
lines.push(`Contracts: ${contracts.length}`);
|
|
224
|
+
for (const c of contracts) {
|
|
225
|
+
const stat = fs.statSync(path.join(contractsDir, c));
|
|
226
|
+
const age = formatAge(stat.mtime);
|
|
227
|
+
lines.push(` đ ${c} (updated ${age})`);
|
|
228
|
+
}
|
|
229
|
+
lines.push('');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Workspace-level tasks
|
|
234
|
+
const wsReadyPath = path.join(workspaceRoot, WORKSPACE_DIR, 'state', 'ready.json');
|
|
235
|
+
if (fs.existsSync(wsReadyPath)) {
|
|
236
|
+
try {
|
|
237
|
+
const wsReady = JSON.parse(fs.readFileSync(wsReadyPath, 'utf-8'));
|
|
238
|
+
const wsInProgress = (wsReady.inProgress || []).length;
|
|
239
|
+
const wsReadyCount = (wsReady.ready || []).length;
|
|
240
|
+
if (wsInProgress > 0 || wsReadyCount > 0) {
|
|
241
|
+
lines.push(`Workspace tasks: ${wsInProgress} in progress, ${wsReadyCount} ready`);
|
|
242
|
+
lines.push('');
|
|
243
|
+
}
|
|
244
|
+
} catch (_err) {
|
|
245
|
+
// Non-critical
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return lines.join('\n');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function formatAge(date) {
|
|
253
|
+
const ms = Date.now() - new Date(date).getTime();
|
|
254
|
+
const minutes = Math.floor(ms / 60000);
|
|
255
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
256
|
+
const hours = Math.floor(minutes / 60);
|
|
257
|
+
if (hours < 24) return `${hours}h ago`;
|
|
258
|
+
const days = Math.floor(hours / 24);
|
|
259
|
+
return `${days}d ago`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ============================================================
|
|
263
|
+
// Add/Remove Members (Criterion 4)
|
|
264
|
+
// ============================================================
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Add a member repo to the workspace
|
|
268
|
+
* @param {string} workspaceRoot
|
|
269
|
+
* @param {string} memberPath â path to the repo
|
|
270
|
+
* @param {string} [role] â optional role override
|
|
271
|
+
* @returns {Object} result
|
|
272
|
+
*/
|
|
273
|
+
function addMember(workspaceRoot, memberPath, role) {
|
|
274
|
+
const configPath = path.join(workspaceRoot, WORKSPACE_CONFIG_FILE);
|
|
275
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
276
|
+
|
|
277
|
+
const absPath = path.resolve(workspaceRoot, memberPath);
|
|
278
|
+
const name = path.basename(absPath);
|
|
279
|
+
const workflowPath = path.join(absPath, '.workflow');
|
|
280
|
+
|
|
281
|
+
if (!fs.existsSync(workflowPath)) {
|
|
282
|
+
throw new Error(`${memberPath} does not have a .workflow/ directory. Run 'flow init' there first.`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (config.members[name]) {
|
|
286
|
+
throw new Error(`Member '${name}' already exists in workspace.`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Read metadata to auto-detect role
|
|
290
|
+
const metadata = readMemberMetadata(workflowPath);
|
|
291
|
+
const endpoints = extractEndpoints(metadata);
|
|
292
|
+
const detectedRole = role || require('./workspace').autoDetectRole(endpoints);
|
|
293
|
+
|
|
294
|
+
config.members[name] = {
|
|
295
|
+
path: `./${name}`,
|
|
296
|
+
role: detectedRole
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
300
|
+
|
|
301
|
+
// Re-sync to update manifest
|
|
302
|
+
syncWorkspace(workspaceRoot, { silent: true });
|
|
303
|
+
|
|
304
|
+
return { name, role: detectedRole, path: memberPath };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Remove a member repo from the workspace
|
|
309
|
+
* @param {string} workspaceRoot
|
|
310
|
+
* @param {string} memberName
|
|
311
|
+
* @returns {boolean} success
|
|
312
|
+
*/
|
|
313
|
+
function removeMember(workspaceRoot, memberName) {
|
|
314
|
+
const configPath = path.join(workspaceRoot, WORKSPACE_CONFIG_FILE);
|
|
315
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
316
|
+
|
|
317
|
+
if (!config.members[memberName]) {
|
|
318
|
+
throw new Error(`Member '${memberName}' not found in workspace.`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
delete config.members[memberName];
|
|
322
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
323
|
+
|
|
324
|
+
// Re-sync to update manifest
|
|
325
|
+
syncWorkspace(workspaceRoot, { silent: true });
|
|
326
|
+
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ============================================================
|
|
331
|
+
// Exports
|
|
332
|
+
// ============================================================
|
|
333
|
+
|
|
334
|
+
module.exports = {
|
|
335
|
+
syncWorkspace,
|
|
336
|
+
getWorkspaceStatus,
|
|
337
|
+
addMember,
|
|
338
|
+
removeMember
|
|
339
|
+
};
|