moflo 4.7.7 → 4.8.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.
Files changed (61) hide show
  1. package/.claude/helpers/statusline.cjs +34 -26
  2. package/.claude/settings.json +2 -2
  3. package/README.md +1 -1
  4. package/bin/hooks.mjs +33 -3
  5. package/bin/session-start-launcher.mjs +88 -3
  6. package/package.json +3 -5
  7. package/src/@claude-flow/cli/README.md +1 -1
  8. package/src/@claude-flow/cli/dist/src/commands/daemon.js +42 -95
  9. package/src/@claude-flow/cli/dist/src/commands/doctor.js +11 -5
  10. package/src/@claude-flow/cli/dist/src/commands/init.js +0 -145
  11. package/src/@claude-flow/cli/dist/src/config/moflo-config.d.ts +5 -0
  12. package/src/@claude-flow/cli/dist/src/config/moflo-config.js +16 -0
  13. package/src/@claude-flow/cli/dist/src/config-adapter.d.ts +1 -1
  14. package/src/@claude-flow/cli/dist/src/init/executor.js +74 -7
  15. package/src/@claude-flow/cli/dist/src/init/mcp-generator.d.ts +3 -4
  16. package/src/@claude-flow/cli/dist/src/init/mcp-generator.js +65 -22
  17. package/src/@claude-flow/cli/dist/src/init/types.d.ts +0 -4
  18. package/src/@claude-flow/cli/dist/src/init/types.js +0 -5
  19. package/src/@claude-flow/cli/dist/src/mcp-server.js +36 -0
  20. package/src/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +6 -0
  21. package/src/@claude-flow/cli/dist/src/memory/memory-bridge.js +66 -0
  22. package/src/@claude-flow/cli/dist/src/memory/memory-initializer.js +52 -1
  23. package/src/@claude-flow/cli/dist/src/services/daemon-lock.d.ts +39 -0
  24. package/src/@claude-flow/cli/dist/src/services/daemon-lock.js +213 -0
  25. package/src/@claude-flow/cli/package.json +2 -6
  26. package/.claude/helpers/README.md +0 -97
  27. package/.claude/helpers/adr-compliance.sh +0 -186
  28. package/.claude/helpers/aggressive-microcompact.mjs +0 -36
  29. package/.claude/helpers/auto-commit.sh +0 -178
  30. package/.claude/helpers/checkpoint-manager.sh +0 -251
  31. package/.claude/helpers/context-persistence-hook.mjs +0 -1979
  32. package/.claude/helpers/daemon-manager.sh +0 -252
  33. package/.claude/helpers/ddd-tracker.sh +0 -144
  34. package/.claude/helpers/github-safe.js +0 -106
  35. package/.claude/helpers/github-setup.sh +0 -28
  36. package/.claude/helpers/guidance-hook.sh +0 -13
  37. package/.claude/helpers/guidance-hooks.sh +0 -102
  38. package/.claude/helpers/health-monitor.sh +0 -108
  39. package/.claude/helpers/learning-hooks.sh +0 -329
  40. package/.claude/helpers/learning-optimizer.sh +0 -127
  41. package/.claude/helpers/learning-service.mjs +0 -1211
  42. package/.claude/helpers/memory.cjs +0 -84
  43. package/.claude/helpers/metrics-db.mjs +0 -492
  44. package/.claude/helpers/patch-aggressive-prune.mjs +0 -184
  45. package/.claude/helpers/pattern-consolidator.sh +0 -86
  46. package/.claude/helpers/perf-worker.sh +0 -160
  47. package/.claude/helpers/quick-start.sh +0 -19
  48. package/.claude/helpers/router.cjs +0 -62
  49. package/.claude/helpers/security-scanner.sh +0 -127
  50. package/.claude/helpers/session.cjs +0 -125
  51. package/.claude/helpers/setup-mcp.sh +0 -18
  52. package/.claude/helpers/standard-checkpoint-hooks.sh +0 -189
  53. package/.claude/helpers/swarm-comms.sh +0 -353
  54. package/.claude/helpers/swarm-hooks.sh +0 -761
  55. package/.claude/helpers/swarm-monitor.sh +0 -211
  56. package/.claude/helpers/sync-v3-metrics.sh +0 -245
  57. package/.claude/helpers/update-v3-progress.sh +0 -166
  58. package/.claude/helpers/v3-quick-status.sh +0 -58
  59. package/.claude/helpers/v3.sh +0 -111
  60. package/.claude/helpers/validate-v3-config.sh +0 -216
  61. package/.claude/helpers/worker-manager.sh +0 -170
@@ -388,6 +388,9 @@ export async function bridgeStoreEntry(options) {
388
388
  await cacheSet(registry, cacheKey, { id, key, namespace, content: value, embedding: embeddingJson });
389
389
  // Phase 4: AttestationLog write audit
390
390
  await logAttestation(registry, 'store', id, { key, namespace, hasEmbedding: !!embeddingJson });
391
+ // Update statusline vector stats cache (debounced)
392
+ if (embeddingJson)
393
+ refreshVectorStatsCache();
391
394
  return {
392
395
  success: true,
393
396
  id,
@@ -690,6 +693,9 @@ export async function bridgeDeleteEntry(options) {
690
693
  catch {
691
694
  // Non-fatal
692
695
  }
696
+ // Update statusline vector stats cache (debounced)
697
+ if (changes > 0)
698
+ refreshVectorStatsCache();
693
699
  return {
694
700
  success: true,
695
701
  deleted: changes > 0,
@@ -1547,4 +1553,64 @@ function cosineSim(a, b) {
1547
1553
  const mag = Math.sqrt(normA * normB);
1548
1554
  return mag === 0 ? 0 : dot / mag;
1549
1555
  }
1556
+ // ===== Vector stats cache for statusline =====
1557
+ // Written after memory mutations so the statusline can read stats without
1558
+ // spawning a subprocess. Debounced to avoid thrashing during batch operations.
1559
+ /**
1560
+ * Write vector-stats.json cache file used by the statusline.
1561
+ * Synchronous — safe to call from short-lived CLI commands.
1562
+ * Uses the already-initialized registry; no-ops if registry isn't loaded.
1563
+ */
1564
+ export function refreshVectorStatsCache(dbPathOverride) {
1565
+ try {
1566
+ const registry = registryInstance; // Use existing instance only, don't init
1567
+ if (!registry)
1568
+ return;
1569
+ const ctx = getDb(registry);
1570
+ if (!ctx?.db)
1571
+ return;
1572
+ let vectorCount = 0;
1573
+ let namespaces = 0;
1574
+ let dbSizeKB = 0;
1575
+ let hasHnsw = false;
1576
+ try {
1577
+ const countRow = ctx.db.prepare('SELECT COUNT(*) as c FROM memory_entries WHERE status = ? AND embedding IS NOT NULL').get('active');
1578
+ vectorCount = countRow?.c ?? 0;
1579
+ const nsRow = ctx.db.prepare('SELECT COUNT(DISTINCT namespace) as n FROM memory_entries WHERE status = ?').get('active');
1580
+ namespaces = nsRow?.n ?? 0;
1581
+ }
1582
+ catch {
1583
+ // Table may not exist yet
1584
+ }
1585
+ // DB file size
1586
+ const dbFile = dbPathOverride || getDbPath();
1587
+ try {
1588
+ const stat = fs.statSync(dbFile);
1589
+ dbSizeKB = Math.floor(stat.size / 1024);
1590
+ }
1591
+ catch { /* file may not exist */ }
1592
+ // HNSW index presence
1593
+ const root = getProjectRoot();
1594
+ const hnswPaths = [
1595
+ path.join(root, '.swarm', 'hnsw.index'),
1596
+ path.join(root, '.claude-flow', 'hnsw.index'),
1597
+ ];
1598
+ for (const p of hnswPaths) {
1599
+ try {
1600
+ fs.statSync(p);
1601
+ hasHnsw = true;
1602
+ break;
1603
+ }
1604
+ catch { /* nope */ }
1605
+ }
1606
+ // Write cache file
1607
+ const cacheDir = path.join(root, '.claude-flow');
1608
+ if (!fs.existsSync(cacheDir))
1609
+ fs.mkdirSync(cacheDir, { recursive: true });
1610
+ fs.writeFileSync(path.join(cacheDir, 'vector-stats.json'), JSON.stringify({ vectorCount, dbSizeKB, namespaces, hasHnsw, updatedAt: Date.now() }));
1611
+ }
1612
+ catch {
1613
+ // Non-fatal — statusline falls back to file size estimate
1614
+ }
1615
+ }
1550
1616
  //# sourceMappingURL=memory-bridge.js.map
@@ -10,6 +10,41 @@
10
10
  */
11
11
  import * as fs from 'fs';
12
12
  import * as path from 'path';
13
+ /**
14
+ * Write vector-stats.json cache for the statusline (no subprocess needed).
15
+ * Called after memory store/delete to keep the cache fresh.
16
+ * @param dbPath - path to the SQLite database file
17
+ * @param stats - optional exact counts from a db query already in progress
18
+ */
19
+ function writeVectorStatsCache(dbPath, stats) {
20
+ try {
21
+ const fileStat = fs.statSync(dbPath);
22
+ const dbSizeKB = Math.floor(fileStat.size / 1024);
23
+ const vectorCount = stats?.vectorCount ?? 0;
24
+ const namespaces = stats?.namespaces ?? 0;
25
+ // Check HNSW index presence
26
+ const dbDir = path.dirname(dbPath);
27
+ const projectDir = path.dirname(dbDir); // .swarm -> project root
28
+ let hasHnsw = false;
29
+ for (const p of [
30
+ path.join(dbDir, 'hnsw.index'),
31
+ path.join(projectDir, '.claude-flow', 'hnsw.index'),
32
+ ]) {
33
+ try {
34
+ fs.statSync(p);
35
+ hasHnsw = true;
36
+ break;
37
+ }
38
+ catch { /* nope */ }
39
+ }
40
+ // Write to .claude-flow dir next to the .swarm dir
41
+ const cacheDir = path.join(projectDir, '.claude-flow');
42
+ if (!fs.existsSync(cacheDir))
43
+ fs.mkdirSync(cacheDir, { recursive: true });
44
+ fs.writeFileSync(path.join(cacheDir, 'vector-stats.json'), JSON.stringify({ vectorCount, dbSizeKB, namespaces, hasHnsw, updatedAt: Date.now() }));
45
+ }
46
+ catch { /* Non-fatal */ }
47
+ }
13
48
  // ADR-053: Lazy import of AgentDB v3 bridge
14
49
  let _bridge;
15
50
  async function getBridge() {
@@ -1747,8 +1782,13 @@ export async function storeEntry(options) {
1747
1782
  const bridge = await getBridge();
1748
1783
  if (bridge) {
1749
1784
  const bridgeResult = await bridge.bridgeStoreEntry(options);
1750
- if (bridgeResult)
1785
+ if (bridgeResult) {
1786
+ // Update statusline cache after successful bridge store
1787
+ const swarmDir = path.join(process.cwd(), '.swarm');
1788
+ const dbFile = options.dbPath || path.join(swarmDir, 'memory.db');
1789
+ writeVectorStatsCache(dbFile);
1751
1790
  return bridgeResult;
1791
+ }
1752
1792
  }
1753
1793
  // Fallback: raw sql.js
1754
1794
  const { key, value, namespace = 'default', generateEmbeddingFlag = true, tags = [], ttl, dbPath: customPath, upsert = false } = options;
@@ -1805,6 +1845,15 @@ export async function storeEntry(options) {
1805
1845
  // Save
1806
1846
  const data = db.export();
1807
1847
  fs.writeFileSync(dbPath, Buffer.from(data));
1848
+ // Query exact stats while DB is still open
1849
+ let vecCount = 0, nsCount = 0;
1850
+ try {
1851
+ const vc = db.exec("SELECT COUNT(*) FROM memory_entries WHERE status='active' AND embedding IS NOT NULL");
1852
+ vecCount = vc[0]?.values?.[0]?.[0] ?? 0;
1853
+ const nc = db.exec("SELECT COUNT(DISTINCT namespace) FROM memory_entries WHERE status='active'");
1854
+ nsCount = nc[0]?.values?.[0]?.[0] ?? 0;
1855
+ }
1856
+ catch { /* table may not have status column in older DBs */ }
1808
1857
  db.close();
1809
1858
  // Add to HNSW index for faster future searches
1810
1859
  if (embeddingJson) {
@@ -1816,6 +1865,8 @@ export async function storeEntry(options) {
1816
1865
  content: value
1817
1866
  });
1818
1867
  }
1868
+ // Update statusline cache with exact counts
1869
+ writeVectorStatsCache(dbPath, { vectorCount: vecCount, namespaces: nsCount });
1819
1870
  return {
1820
1871
  success: true,
1821
1872
  id,
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Atomic daemon lock — prevents duplicate daemon processes.
3
+ *
4
+ * Uses fs.writeFileSync with { flag: 'wx' } (O_CREAT | O_EXCL) which is
5
+ * atomic on all platforms: the write fails immediately if the file exists,
6
+ * eliminating the TOCTOU race in the old PID-file approach.
7
+ *
8
+ * Also solves Windows PID recycling by storing a label in the lock payload
9
+ * and verifying the process command line before trusting a "live" PID.
10
+ */
11
+ export interface DaemonLockPayload {
12
+ pid: number;
13
+ startedAt: number;
14
+ label: string;
15
+ }
16
+ /** Resolve the lock file path for a project root. */
17
+ export declare function lockPath(projectRoot: string): string;
18
+ /**
19
+ * Try to acquire the daemon lock atomically.
20
+ *
21
+ * @returns `{ acquired: true }` on success,
22
+ * `{ acquired: false, holder: pid }` if another daemon owns the lock.
23
+ */
24
+ export declare function acquireDaemonLock(projectRoot: string, pid?: number): {
25
+ acquired: true;
26
+ } | {
27
+ acquired: false;
28
+ holder: number;
29
+ };
30
+ /**
31
+ * Release the daemon lock. Only removes if we own it (or force = true).
32
+ */
33
+ export declare function releaseDaemonLock(projectRoot: string, pid?: number, force?: boolean): void;
34
+ /**
35
+ * Check if the daemon lock is currently held by a live daemon.
36
+ * Returns the holder PID or null.
37
+ */
38
+ export declare function getDaemonLockHolder(projectRoot: string): number | null;
39
+ //# sourceMappingURL=daemon-lock.d.ts.map
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Atomic daemon lock — prevents duplicate daemon processes.
3
+ *
4
+ * Uses fs.writeFileSync with { flag: 'wx' } (O_CREAT | O_EXCL) which is
5
+ * atomic on all platforms: the write fails immediately if the file exists,
6
+ * eliminating the TOCTOU race in the old PID-file approach.
7
+ *
8
+ * Also solves Windows PID recycling by storing a label in the lock payload
9
+ * and verifying the process command line before trusting a "live" PID.
10
+ */
11
+ import * as fs from 'fs';
12
+ import { join } from 'path';
13
+ import { execSync } from 'child_process';
14
+ const LOCK_FILENAME = 'daemon.lock';
15
+ const LOCK_LABEL = 'moflo-daemon';
16
+ /** Resolve the lock file path for a project root. */
17
+ export function lockPath(projectRoot) {
18
+ return join(projectRoot, '.claude-flow', LOCK_FILENAME);
19
+ }
20
+ /**
21
+ * Try to acquire the daemon lock atomically.
22
+ *
23
+ * @returns `{ acquired: true }` on success,
24
+ * `{ acquired: false, holder: pid }` if another daemon owns the lock.
25
+ */
26
+ export function acquireDaemonLock(projectRoot, pid = process.pid) {
27
+ const lock = lockPath(projectRoot);
28
+ const stateDir = join(projectRoot, '.claude-flow');
29
+ // Ensure state directory exists
30
+ if (!fs.existsSync(stateDir)) {
31
+ fs.mkdirSync(stateDir, { recursive: true });
32
+ }
33
+ const payload = {
34
+ pid,
35
+ startedAt: Date.now(),
36
+ label: LOCK_LABEL,
37
+ };
38
+ // Attempt 1: atomic exclusive create
39
+ const result = tryExclusiveWrite(lock, payload);
40
+ if (result === 'ok') {
41
+ return { acquired: true };
42
+ }
43
+ // File already exists — check if the holder is still a live daemon
44
+ const existing = readLockPayload(lock);
45
+ if (!existing) {
46
+ // Corrupt or unreadable — remove and retry once
47
+ safeUnlink(lock);
48
+ return tryExclusiveWrite(lock, payload) === 'ok'
49
+ ? { acquired: true }
50
+ : { acquired: false, holder: -1 };
51
+ }
52
+ // Same PID as us? We already hold it (re-entrant).
53
+ if (existing.pid === pid) {
54
+ return { acquired: true };
55
+ }
56
+ // Is the process alive AND actually a moflo daemon?
57
+ if (isProcessAlive(existing.pid) && isDaemonProcess(existing.pid)) {
58
+ return { acquired: false, holder: existing.pid };
59
+ }
60
+ // Stale lock (dead process or recycled PID) — remove and retry once
61
+ safeUnlink(lock);
62
+ return tryExclusiveWrite(lock, payload) === 'ok'
63
+ ? { acquired: true }
64
+ : { acquired: false, holder: -1 };
65
+ }
66
+ /**
67
+ * Release the daemon lock. Only removes if we own it (or force = true).
68
+ */
69
+ export function releaseDaemonLock(projectRoot, pid = process.pid, force = false) {
70
+ const lock = lockPath(projectRoot);
71
+ if (!fs.existsSync(lock))
72
+ return;
73
+ if (force) {
74
+ safeUnlink(lock);
75
+ return;
76
+ }
77
+ const existing = readLockPayload(lock);
78
+ if (existing && existing.pid === pid) {
79
+ safeUnlink(lock);
80
+ }
81
+ }
82
+ /**
83
+ * Check if the daemon lock is currently held by a live daemon.
84
+ * Returns the holder PID or null.
85
+ */
86
+ export function getDaemonLockHolder(projectRoot) {
87
+ const lock = lockPath(projectRoot);
88
+ if (!fs.existsSync(lock))
89
+ return null;
90
+ const existing = readLockPayload(lock);
91
+ if (!existing) {
92
+ // Corrupt lock file — clean it up
93
+ safeUnlink(lock);
94
+ return null;
95
+ }
96
+ if (isProcessAlive(existing.pid) && isDaemonProcess(existing.pid)) {
97
+ return existing.pid;
98
+ }
99
+ // Stale — clean it up opportunistically
100
+ safeUnlink(lock);
101
+ return null;
102
+ }
103
+ // ---------------------------------------------------------------------------
104
+ // Internal helpers
105
+ // ---------------------------------------------------------------------------
106
+ function tryExclusiveWrite(path, payload) {
107
+ try {
108
+ fs.writeFileSync(path, JSON.stringify(payload), { flag: 'wx' });
109
+ return 'ok';
110
+ }
111
+ catch (err) {
112
+ if (err.code === 'EEXIST')
113
+ return 'exists';
114
+ // Other errors (permissions, disk full) — treat as failure to acquire
115
+ return 'exists';
116
+ }
117
+ }
118
+ function readLockPayload(path) {
119
+ try {
120
+ const raw = fs.readFileSync(path, 'utf-8');
121
+ const data = JSON.parse(raw);
122
+ if (typeof data.pid === 'number' && typeof data.startedAt === 'number') {
123
+ return data;
124
+ }
125
+ return null;
126
+ }
127
+ catch {
128
+ return null;
129
+ }
130
+ }
131
+ function safeUnlink(path) {
132
+ try {
133
+ fs.unlinkSync(path);
134
+ }
135
+ catch { /* ignore — file may already be gone */ }
136
+ }
137
+ function isProcessAlive(pid) {
138
+ try {
139
+ process.kill(pid, 0);
140
+ return true;
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ }
146
+ /**
147
+ * Cross-platform check: is this PID actually a moflo/claude-flow daemon?
148
+ *
149
+ * This prevents false positives from Windows PID recycling, where a dead
150
+ * daemon's PID gets reused by an unrelated process (e.g. Chrome).
151
+ *
152
+ * - Windows: uses `tasklist /FI` to check the process image + command line
153
+ * - Linux: reads /proc/<pid>/cmdline
154
+ * - macOS: uses `ps -p <pid> -o command=`
155
+ *
156
+ * Falls back to `true` (trust process.kill) if the platform check fails,
157
+ * to avoid accidentally allowing duplicates on exotic platforms.
158
+ */
159
+ function isDaemonProcess(pid) {
160
+ try {
161
+ if (process.platform === 'win32') {
162
+ return isDaemonProcessWindows(pid);
163
+ }
164
+ else if (process.platform === 'linux') {
165
+ return isDaemonProcessLinux(pid);
166
+ }
167
+ else {
168
+ // macOS and others
169
+ return isDaemonProcessUnix(pid);
170
+ }
171
+ }
172
+ catch {
173
+ // If platform check fails, trust the kill(0) result to avoid
174
+ // accidentally allowing duplicates
175
+ return true;
176
+ }
177
+ }
178
+ function isDaemonProcessWindows(pid) {
179
+ try {
180
+ const result = execSync(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, { encoding: 'utf-8', timeout: 3000, windowsHide: true });
181
+ // tasklist returns the image name + PID in CSV; check it's a node process
182
+ // and then verify via wmic/powershell that the command line contains daemon keywords
183
+ if (!result.includes('node'))
184
+ return false;
185
+ const cmdResult = execSync(`powershell -NoProfile -Command "(Get-CimInstance Win32_Process -Filter \\"ProcessId=${pid}\\").CommandLine"`, { encoding: 'utf-8', timeout: 5000, windowsHide: true });
186
+ return /daemon\s+start|moflo|claude-flow/i.test(cmdResult);
187
+ }
188
+ catch {
189
+ return true; // fallback: trust kill(0)
190
+ }
191
+ }
192
+ function isDaemonProcessLinux(pid) {
193
+ try {
194
+ const cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, 'utf-8');
195
+ return /daemon.*start|moflo|claude-flow/i.test(cmdline);
196
+ }
197
+ catch {
198
+ return true; // fallback
199
+ }
200
+ }
201
+ function isDaemonProcessUnix(pid) {
202
+ try {
203
+ const result = execSync(`ps -p ${pid} -o command=`, {
204
+ encoding: 'utf-8',
205
+ timeout: 3000,
206
+ });
207
+ return /daemon.*start|moflo|claude-flow/i.test(result);
208
+ }
209
+ catch {
210
+ return true; // fallback
211
+ }
212
+ }
213
+ //# sourceMappingURL=daemon-lock.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moflo/cli",
3
- "version": "4.7.7",
3
+ "version": "4.8.0",
4
4
  "type": "module",
5
5
  "description": "MoFlo CLI — AI agent orchestration with specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",
@@ -81,18 +81,14 @@
81
81
  "publish:all": "./scripts/publish.sh"
82
82
  },
83
83
  "devDependencies": {
84
- "typescript": "^5.3.0",
85
- "vitest": "^4.0.16"
84
+ "typescript": "^5.3.0"
86
85
  },
87
86
  "dependencies": {
88
- "@claude-flow/mcp": "^3.0.0-alpha.8",
89
- "@claude-flow/shared": "^3.0.0-alpha.1",
90
87
  "@noble/ed25519": "^2.1.0",
91
88
  "semver": "^7.6.0"
92
89
  },
93
90
  "optionalDependencies": {
94
91
  "@claude-flow/aidefence": "^3.0.2",
95
- "@claude-flow/codex": "^3.0.0-alpha.8",
96
92
  "@claude-flow/embeddings": "^3.0.0-alpha.12",
97
93
  "@claude-flow/guidance": "^3.0.0-alpha.1",
98
94
  "@claude-flow/memory": "^3.0.0-alpha.11",
@@ -1,97 +0,0 @@
1
- # RuFlo V3 Helpers
2
-
3
- This directory contains helper scripts and utilities for V3 development.
4
-
5
- ## 🚀 Quick Start
6
-
7
- ```bash
8
- # Initialize V3 development environment
9
- .claude/helpers/v3.sh init
10
-
11
- # Quick status check
12
- .claude/helpers/v3.sh status
13
-
14
- # Update progress metrics
15
- .claude/helpers/v3.sh update domain 3
16
- .claude/helpers/v3.sh update agent 8
17
- .claude/helpers/v3.sh update security 2
18
- ```
19
-
20
- ## Available Helpers
21
-
22
- ### 🎛️ V3 Master Tool
23
- - **`v3.sh`** - Main command-line interface for all V3 operations
24
- ```bash
25
- .claude/helpers/v3.sh help # Show all commands
26
- .claude/helpers/v3.sh status # Quick development status
27
- .claude/helpers/v3.sh update domain 3 # Update specific metrics
28
- .claude/helpers/v3.sh validate # Validate configuration
29
- .claude/helpers/v3.sh full-status # Complete status overview
30
- ```
31
-
32
- ### 📊 V3 Progress Management
33
- - **`update-v3-progress.sh`** - Update V3 development metrics
34
- ```bash
35
- # Usage examples:
36
- .claude/helpers/update-v3-progress.sh domain 3 # Mark 3 domains complete
37
- .claude/helpers/update-v3-progress.sh agent 8 # 8 agents active
38
- .claude/helpers/update-v3-progress.sh security 2 # 2 CVEs fixed
39
- .claude/helpers/update-v3-progress.sh performance 2.5x # Performance boost
40
- .claude/helpers/update-v3-progress.sh status # Show current status
41
- ```
42
-
43
- ### 🔍 Configuration Validation
44
- - **`validate-v3-config.sh`** - Comprehensive environment validation
45
- - Checks all required directories and files
46
- - Validates JSON configuration files
47
- - Verifies Node.js and development tools
48
- - Confirms Git repository status
49
- - Validates file permissions
50
-
51
- ### ⚡ Quick Status
52
- - **`v3-quick-status.sh`** - Compact development progress overview
53
- - Shows domain, agent, and DDD progress
54
- - Displays security and performance metrics
55
- - Color-coded status indicators
56
- - Current Git branch information
57
-
58
- ## Helper Script Standards
59
-
60
- ### File Naming
61
- - Use kebab-case: `update-v3-progress.sh`
62
- - Include version prefix: `v3-*` for V3-specific helpers
63
- - Use descriptive names that indicate purpose
64
-
65
- ### Script Requirements
66
- - Must be executable (`chmod +x`)
67
- - Include proper error handling (`set -e`)
68
- - Provide usage help when called without arguments
69
- - Use consistent exit codes (0 = success, non-zero = error)
70
-
71
- ### Configuration Integration
72
- Helpers are configured in `.claude/settings.json`:
73
- ```json
74
- {
75
- "helpers": {
76
- "directory": ".claude/helpers",
77
- "enabled": true,
78
- "v3ProgressUpdater": ".claude/helpers/update-v3-progress.sh"
79
- }
80
- }
81
- ```
82
-
83
- ## Development Guidelines
84
-
85
- 1. **Security First**: All helpers must validate inputs
86
- 2. **Idempotent**: Scripts should be safe to run multiple times
87
- 3. **Fast Execution**: Keep helper execution under 1 second when possible
88
- 4. **Clear Output**: Provide clear success/error messages
89
- 5. **JSON Safe**: When updating JSON files, use `jq` for safety
90
-
91
- ## Adding New Helpers
92
-
93
- 1. Create script in `.claude/helpers/`
94
- 2. Make executable: `chmod +x script-name.sh`
95
- 3. Add to settings.json helpers section
96
- 4. Test thoroughly before committing
97
- 5. Update this README with usage documentation