moflo 4.8.83 → 4.8.85

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": "moflo",
3
- "version": "4.8.83",
3
+ "version": "4.8.85",
4
4
  "description": "MoFlo — AI agent orchestration for Claude Code. Forked from ruflo/claude-flow with patches applied to source, plus feature-level orchestration.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -117,7 +117,7 @@
117
117
  "@types/node": "^24.12.2",
118
118
  "@xenova/transformers": "^2.17.0",
119
119
  "eslint": "^8.0.0",
120
- "moflo": "^4.8.82",
120
+ "moflo": "^4.8.84",
121
121
  "tsx": "^4.21.0",
122
122
  "typescript": "^5.9.3",
123
123
  "vitest": "^4.0.0"
@@ -120,6 +120,18 @@ steps:
120
120
  # The github `comment` action only appends a comment — it does NOT
121
121
  # touch the task-list checkboxes. We read the body, substitute, and
122
122
  # write it back so the epic reflects progress at a glance.
123
+ #
124
+ # Cross-platform notes:
125
+ # - Uses `node -e` instead of sed/awk/tr because coreutils are
126
+ # not guaranteed on PATH (minimal containers, some Git-Bash
127
+ # installs). Node is always available — moflo runs on it.
128
+ # - Values reach node via env vars (not argv) so the script is
129
+ # immune to Node's argv[1]-"[eval]" quirk across versions.
130
+ # - The embedded JS contains ZERO backslashes. When Node spawns
131
+ # `bash -c '<script>'` on Windows, Cygwin/MSYS bash strips one
132
+ # layer of backslash escapes from the command line, which
133
+ # silently broke regex escapes like `\[` and `\b`. Using
134
+ # indexOf + a char-code `isWord` check avoids the trap entirely.
123
135
  - id: check-off-story
124
136
  type: bash
125
137
  permissionLevel: elevated
@@ -128,11 +140,42 @@ steps:
128
140
  set -e
129
141
  STORY={loop.story_number}
130
142
  EPIC={args.epic_number}
131
- BODY=$(gh issue view "$EPIC" --json body -q .body)
132
- UPDATED=$(printf '%s' "$BODY" | sed -E "s/- \[ \] #${STORY}\b/- [x] #${STORY}/")
133
- if [ "$BODY" != "$UPDATED" ]; then
134
- printf '%s' "$UPDATED" | gh issue edit "$EPIC" --body-file -
135
- fi
143
+ export STORY EPIC
144
+ node -e '
145
+ const cp = require("node:child_process");
146
+ const epic = process.env.EPIC;
147
+ const story = process.env.STORY;
148
+ const view = cp.spawnSync("gh", ["issue", "view", epic, "--json", "body", "-q", ".body"], { encoding: "utf8" });
149
+ if (view.status !== 0) {
150
+ process.stderr.write(view.stderr || "gh issue view failed");
151
+ process.exit(view.status || 1);
152
+ }
153
+ const body = view.stdout;
154
+ const target = "- [ ] #" + story;
155
+ const replacement = "- [x] #" + story;
156
+ function isWord(c) {
157
+ if (!c) return false;
158
+ const n = c.charCodeAt(0);
159
+ return (n >= 48 && n <= 57) || (n >= 65 && n <= 90) || (n >= 97 && n <= 122) || n === 95;
160
+ }
161
+ let updated = "";
162
+ let i = 0;
163
+ while (i < body.length) {
164
+ const idx = body.indexOf(target, i);
165
+ if (idx < 0) { updated += body.slice(i); break; }
166
+ updated += body.slice(i, idx);
167
+ const end = idx + target.length;
168
+ if (isWord(body[end])) {
169
+ updated += body.slice(idx, end);
170
+ } else {
171
+ updated += replacement;
172
+ }
173
+ i = end;
174
+ }
175
+ if (updated === body) process.exit(0);
176
+ const edit = cp.spawnSync("gh", ["issue", "edit", epic, "--body-file", "-"], { input: updated, stdio: ["pipe", "inherit", "inherit"] });
177
+ if (edit.status !== 0) process.exit(edit.status || 1);
178
+ '
136
179
  timeout: 60000
137
180
  failOnError: true
138
181
 
@@ -178,11 +221,20 @@ steps:
178
221
  # merging the consolidated PR auto-closes each individual story issue,
179
222
  # not just the epic. Without this, only the epic would close on merge
180
223
  # and story issues would linger open indefinitely.
224
+ #
225
+ # Cross-platform: uses `node -e` instead of tr/awk (see check-off-story
226
+ # for rationale).
181
227
  - id: build-closes-list
182
228
  type: bash
183
229
  config:
184
230
  command: |
185
- echo {args.stories} | tr ',' '\n' | awk 'NF {printf "Closes #%s ", $0}'
231
+ STORIES={args.stories}
232
+ export STORIES
233
+ node -e '
234
+ const raw = process.env.STORIES || "";
235
+ const stories = raw.split(",").map(s => s.trim()).filter(Boolean);
236
+ process.stdout.write(stories.map(s => "Closes #" + s).join(" "));
237
+ '
186
238
  timeout: 30000
187
239
  failOnError: true
188
240
 
@@ -87,7 +87,11 @@ export async function getRegistry(dbPath) {
87
87
  if (!registryPromise) {
88
88
  registryPromise = (async () => {
89
89
  try {
90
- const { ControllerRegistry } = await importMofloMemory(import.meta.url);
90
+ const memoryModule = await importMofloMemory();
91
+ if (!memoryModule) {
92
+ throw new Error('@moflo/memory not available (src/modules/memory/dist/index.js not found)');
93
+ }
94
+ const { ControllerRegistry } = memoryModule;
91
95
  const registry = new ControllerRegistry();
92
96
  // Suppress noisy init logs
93
97
  const origLog = console.log;
@@ -381,8 +381,8 @@ export async function getHNSWIndex(options) {
381
381
  // Use HnswLite pure TS implementation (no native dependencies). The
382
382
  // shared resolver handles the consumer case where @moflo/memory is not
383
383
  // a declared dep and must be loaded via a relative URL fallback.
384
- const memoryModule = await importMofloMemory(import.meta.url);
385
- if (!('HnswLite' in memoryModule) || memoryModule.HnswLite === undefined) {
384
+ const memoryModule = await importMofloMemory();
385
+ if (!memoryModule || !('HnswLite' in memoryModule) || memoryModule.HnswLite === undefined) {
386
386
  // Shape-check (issue #482): warn loudly and bail — the outer catch
387
387
  // would otherwise swallow a cryptic "undefined is not a constructor".
388
388
  console.warn('[getHNSWIndex] @moflo/memory missing expected export: HnswLite');
@@ -13,6 +13,8 @@
13
13
  */
14
14
  import { createRequire } from 'module';
15
15
  import { fileURLToPath, pathToFileURL } from 'url';
16
+ import { existsSync } from 'fs';
17
+ import { dirname, join } from 'path';
16
18
  // createRequire anchored to this file — resolves from moflo's own node_modules
17
19
  const mofloRequire = createRequire(fileURLToPath(import.meta.url));
18
20
  /**
@@ -79,20 +81,63 @@ export function mofloResolve(specifier) {
79
81
  }
80
82
  }
81
83
  /**
82
- * Import `@moflo/memory` from within a moflo source module. The root `moflo`
83
- * package ships @moflo/memory as a source folder rather than a declared
84
- * dependency, so `mofloImport('@moflo/memory')` fails in consumer installs
85
- * (node_modules/@moflo/memory/ doesn't exist). Fall back to a URL resolved
86
- * relative to the caller's file — the same src/modules/memory/dist/index.js
87
- * layout holds in both dev and consumer.
84
+ * Locate `src/modules/memory/dist/index.js` by walking up from this file's
85
+ * directory until the path resolves. Layout-invariant across:
86
+ * - dev source (cli/src/services/)
87
+ * - built output (cli/dist/src/services/)
88
+ * - installed package (node_modules/moflo/src/modules/cli/dist/src/services/)
89
+ * - Windows and POSIX (path.join/dirname are platform-aware)
88
90
  *
89
- * @param callerUrl `import.meta.url` of the file that needs @moflo/memory
91
+ * Returns a file:// URL for ESM `import()`, or null if memory isn't built.
90
92
  */
91
- export async function importMofloMemory(callerUrl) {
93
+ let cachedMemoryUrl;
94
+ function locateMofloMemoryDist() {
95
+ if (cachedMemoryUrl !== undefined)
96
+ return cachedMemoryUrl;
97
+ let dir = dirname(fileURLToPath(import.meta.url));
98
+ const rel = join('src', 'modules', 'memory', 'dist', 'index.js');
99
+ // 12 levels is far more than any real moflo install depth
100
+ for (let i = 0; i < 12; i++) {
101
+ const candidate = join(dir, rel);
102
+ if (existsSync(candidate)) {
103
+ cachedMemoryUrl = pathToFileURL(candidate).href;
104
+ return cachedMemoryUrl;
105
+ }
106
+ const parent = dirname(dir);
107
+ if (parent === dir)
108
+ break; // filesystem root
109
+ dir = parent;
110
+ }
111
+ cachedMemoryUrl = null;
112
+ return null;
113
+ }
114
+ /**
115
+ * Import `@moflo/memory` from within a moflo source module.
116
+ *
117
+ * The root `moflo` package ships @moflo/memory as a source folder rather than
118
+ * a declared dependency, so `mofloImport('@moflo/memory')` fails in consumer
119
+ * installs (node_modules/@moflo/memory/ doesn't exist). Fall back to a
120
+ * layout-invariant walk-up that finds `src/modules/memory/dist/index.js`
121
+ * regardless of whether the caller is running source, built, or installed.
122
+ *
123
+ * Returns null when memory isn't available — callers must handle that.
124
+ */
125
+ export async function importMofloMemory() {
92
126
  const viaRequire = await mofloImport('@moflo/memory');
93
127
  if (viaRequire)
94
128
  return viaRequire;
95
- const memoryUrl = new URL('../../../../memory/dist/index.js', callerUrl);
96
- return import(memoryUrl.href);
129
+ const url = locateMofloMemoryDist();
130
+ if (!url)
131
+ return null;
132
+ try {
133
+ return await import(url);
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ }
139
+ // Test-only: reset the cache between unit tests that mutate the filesystem.
140
+ export function _resetMofloMemoryCacheForTest() {
141
+ cachedMemoryUrl = undefined;
97
142
  }
98
143
  //# sourceMappingURL=moflo-require.js.map
@@ -2,5 +2,5 @@
2
2
  * Auto-generated by build. Do not edit manually.
3
3
  * Source of truth: root package.json → scripts/sync-version.mjs
4
4
  */
5
- export const VERSION = '4.8.83';
5
+ export const VERSION = '4.8.85';
6
6
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moflo/cli",
3
- "version": "4.8.83",
3
+ "version": "4.8.85",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -7,12 +7,19 @@
7
7
  /**
8
8
  * Matches `{path}` variable references in spell step configs.
9
9
  *
10
- * The `(?<!\$)` negative lookbehind skips `${VAR}` — that's bash parameter
11
- * expansion inside shell steps, not a spell template reference. Without it,
12
- * a bash command like `sed "s/#${STORY}/.../"` would try to resolve `STORY`
13
- * as a spell variable and fail.
10
+ * Content must be identifier-shape: letter/underscore followed by
11
+ * letters, digits, `_`, `.`, or `-`. This tight grammar accommodates
12
+ * every real spell ref (`{args.x}`, `{loop.x}`, `{step-id.out}`) while
13
+ * letting bash steps embed literal `{...}` blocks — JS destructuring
14
+ * `{ foo }` (whitespace), object literals `{ a: b }` (colon), shell
15
+ * expansions `${VAR}` (lookbehind), and so on — without tripping the
16
+ * interpolator. A greedy `[^}]+` class ate JS code inside `node -e`
17
+ * scripts and threw "Variable not found" at runtime.
18
+ *
19
+ * The `(?<!\$)` negative lookbehind also skips `${VAR}` — bash parameter
20
+ * expansion inside shell steps must pass through untouched.
14
21
  */
15
- export const VAR_REF_PATTERN = /(?<!\$)\{([^}]+)\}/g;
22
+ export const VAR_REF_PATTERN = /(?<!\$)\{([A-Za-z_][A-Za-z0-9_.-]*)\}/g;
16
23
  /**
17
24
  * Resolve a dot-separated path against the context variables.
18
25
  * Returns undefined if any segment is missing.