moflo 4.11.6 → 4.11.8

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.
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Rule (MUST)
6
6
 
7
- When a search hit carries a non-null `navigation` field, you MUST traverse via `mcp__moflo__memory_get_neighbors` NOT bulk-retrieve every hit with `memory_retrieve`. The `navigation` crumb is the chunking architecture's contract; bulk-retrieving every search hit defeats it and is a protocol violation. Use `memory_retrieve` only for a single specific key you already hold, or for non-chunk entries where `navigation` is null.
7
+ When a search hit carries a non-null `navigation` field and you need adjacent context, prefer `memory_search` with `expand: 'neighbors'` — it inlines each chunk hit's prev/next content in the SAME call (one round-trip, no re-embedding). Otherwise traverse via `mcp__moflo__memory_get_neighbors`. NEVER bulk-retrieve every hit with `memory_retrieve` the `navigation` crumb is the chunking architecture's contract, and bulk-retrieving defeats it (protocol violation). NEVER `Read` a chunk's `parentPath` for surrounding context when `expand`/neighbors would do — a full-doc Read costs far more tokens. Use `memory_retrieve` only for a single specific key you already hold, or for non-chunk entries where `navigation` is null.
8
8
 
9
9
  ---
10
10
 
@@ -146,7 +146,8 @@ Once you have a search hit, pick the right follow-up call by what you need next.
146
146
  | You want | Use | Why |
147
147
  |----------|-----|-----|
148
148
  | Find an entry-point | `mcp__moflo__memory_search` | Returns chunk hits with `navigation` (parentDoc, prev/next, chunkTitle) |
149
- | Adjacent context (1 chunk over) | `mcp__moflo__memory_get_neighbors` `{ key, include: ['prev','next'] }` | One round-trip, shaped entries with full nav |
149
+ | Adjacent context AND you're still searching | `mcp__moflo__memory_search` `{ query, expand: 'neighbors' }` | Cheapest prev/next content inlined in the search call itself, zero extra round-trips |
150
+ | Adjacent context for a hit you already have | `mcp__moflo__memory_get_neighbors` `{ key, include: ['prev','next'] }` | One round-trip, shaped entries with full nav |
150
151
  | Same-section peers (h2/h3 family) | `memory_get_neighbors` `{ include: ['siblings'] }` or `['parent','children']` | Hierarchical traversal — cheaper than re-searching |
151
152
  | Full content of one specific chunk | `mcp__moflo__memory_retrieve` `{ key }` | For a key you already hold — never for bulk-retrieving search hits |
152
153
  | Whole source doc when truly needed | `Read` `parentPath` from any chunk's nav | Disk read is cheaper than re-indexed `doc-*` |
@@ -224,6 +224,14 @@ gh issue edit <issue-number> --remove-label "in-progress" --add-label "ready-for
224
224
  gh issue comment <issue-number> --body "PR created: <pr-url>"
225
225
  ```
226
226
 
227
+ If the story body carries an `Epic: #<n>` back-reference (decomposition writes it — `./ticket.md`) **and** `--epic-branch` is not set, sync the parent epic — check this story's box off and close the epic if it was the last:
228
+
229
+ ```bash
230
+ flo epic checkoff <epic-number> <story-number>
231
+ ```
232
+
233
+ Idempotent and safe to skip when there is no back-reference. `--epic-branch` runs skip it — the epic orchestrator owns checklist state there. The box flips when the work is delivered (PR opened), matching the orchestrator; if a PR is later rejected, reopen the epic manually.
234
+
227
235
  ### 5.5 Finalize run record (Flo Runs dashboard)
228
236
 
229
237
  Update the tasklist row written in Phase 0 with the terminal status. Same `runId`, `upsert: true`. On success:
@@ -35,25 +35,47 @@ When promoting to epic:
35
35
  2. Each story should be completable in a single PR.
36
36
  3. Stories should have clear boundaries (one concern per story).
37
37
  4. Order stories by dependency (independent ones first).
38
- 5. Create each story as a GitHub issue with its own Description, Acceptance Criteria, and Test Cases.
39
- 6. Convert the parent issue into an epic with a `## Stories` checklist.
38
+ 5. Establish the epic issue **first** so its number exists before the stories do (either the promoted parent issue or a freshly created epic).
39
+ 6. Create each story as a GitHub issue with its own Description, Acceptance Criteria, Test Cases, **and an `Epic: #<epic-number>` back-reference line at the top of the body**.
40
+ 7. Fill in the epic's `## Stories` checklist with a `- [ ] #<story-number>` line for every story.
41
+
42
+ The **back-reference is load-bearing**, not decoration: a story is usually worked on later via a standalone `/flo <story>` run, not through `flo epic run`. That standalone run reads the `Epic: #<n>` line to find its parent, check its box off, and close the epic when it is the last story. Without the back-reference the epic silently drifts out of sync — the exact miss this step exists to prevent. See `./phases.md` Phase 5.6.
40
43
 
41
44
  ## Epic decomposition (score >= 7)
42
45
 
46
+ The order matters: the epic must exist before the stories so each story can carry the `Epic: #<n>` back-reference, and the epic's checklist is filled in last.
47
+
48
+ Pass multi-line bodies as a single literal `--body "..."` string (portable — no `printf`/heredoc, which are not guaranteed on every consumer OS; Rule #1). For very large bodies write the text to a file with the Write tool and use `--body-file <path>`.
49
+
43
50
  ```bash
44
- # Step 1: create each sub-issue
45
- gh issue create --title "Story: <story-title>" --body "<## Description + ## Acceptance Criteria + ## Suggested Test Cases>" --label "story"
46
- # capture the new issue number from output
51
+ # Step 1: establish the epic issue and capture its number (EPIC).
52
+ # Promoting an existing issue it *is* the epic; add the label now:
53
+ gh issue edit <parent-number> --add-label "epic" # EPIC = <parent-number>
54
+ # Or create a fresh epic with an empty Stories section:
55
+ gh issue create --title "Epic: <title>" --label "epic" --body "<## Overview + empty ## Stories section>"
56
+ # capture EPIC = the epic issue number from whichever path above
57
+
58
+ # Step 2: create each story WITH the Epic back-reference as the first line; capture each number.
59
+ # (repeat for all stories, typically 2–6)
60
+ gh issue create --title "Story: <story-title>" --label "story" --body "<Epic: #<EPIC> + ## Description + ## Acceptance Criteria + ## Suggested Test Cases>"
61
+
62
+ # Step 3: fill the epic's ## Stories checklist with a - [ ] #<story> line per story.
63
+ gh issue edit <EPIC> --body "<epic body with the ## Stories checklist filled in>"
64
+ ```
65
+
66
+ **Story body format** — the first line is the back-reference; the standalone `/flo <story>` run (`./phases.md` Phase 5.6) parses `Epic: #<n>` from it:
47
67
 
48
- # Step 2: repeat for all stories (typically 2–6)
68
+ ```markdown
69
+ Epic: #<epic-number>
49
70
 
50
- # Step 3: build the epic body with a checklist referencing every story number
71
+ ## Description
72
+ <what and why>
51
73
 
52
- # Step 4: update an existing issue into an epic
53
- gh issue edit <parent-number> --add-label "epic" --body "<epic body with ## Stories checklist>"
74
+ ## Acceptance Criteria
75
+ - [ ] ...
54
76
 
55
- # Step 5: or create a new epic
56
- gh issue create --title "Epic: <title>" --label "epic" --body "<epic body>"
77
+ ## Suggested Test Cases
78
+ - ...
57
79
  ```
58
80
 
59
81
  **Epic body format** — the `## Stories` checklist with `- [ ] #<number>` is what enables epic detection, story extraction, and progress tracking:
@@ -12,11 +12,12 @@
12
12
  * flo epic run 42 --verbose Echo each step's stdout/stderr after it completes
13
13
  * flo epic status <epic-number> Check progress via memory
14
14
  * flo epic reset <epic-number> Clear epic memory state
15
+ * flo epic checkoff <epic> <story> Check a story off; close the epic if it was the last
15
16
  */
16
17
  import { readFileSync } from 'node:fs';
17
18
  import { execSync } from 'node:child_process';
18
19
  import { join } from 'node:path';
19
- import { isEpicIssue, fetchEpicIssue, extractStories, enrichStoryNames, } from '../epic/index.js';
20
+ import { isEpicIssue, fetchEpicIssue, extractStories, enrichStoryNames, checkOffStoryInEpic, } from '../epic/index.js';
20
21
  import { runEpicSpell, } from '../epic/runner-adapter.js';
21
22
  import { locateMofloModulePath } from '../services/moflo-require.js';
22
23
  import { select } from '../prompt.js';
@@ -362,6 +363,36 @@ async function resetEpic(epicNumber) {
362
363
  return { success: true };
363
364
  }
364
365
  // ============================================================================
366
+ // Subcommand: checkoff
367
+ // ============================================================================
368
+ async function checkOffStory(epicArg, storyArg) {
369
+ const epic = parseInt(epicArg, 10);
370
+ const story = parseInt(storyArg, 10);
371
+ if (isNaN(epic) || isNaN(story)) {
372
+ return { success: false, message: 'Usage: flo epic checkoff <epic-number> <story-number>' };
373
+ }
374
+ try {
375
+ const { checked, epicClosed, alreadyClosed } = await checkOffStoryInEpic(epic, story);
376
+ const parts = [];
377
+ parts.push(checked
378
+ ? `Checked off story #${story} in epic #${epic}.`
379
+ : `Story #${story} was already checked (or not listed) in epic #${epic}.`);
380
+ if (epicClosed)
381
+ parts.push(`All stories complete — closed epic #${epic}.`);
382
+ else if (alreadyClosed)
383
+ parts.push(`Epic #${epic} was already closed.`);
384
+ const message = parts.join(' ');
385
+ console.log(`[epic] ${message}`);
386
+ return { success: true, message };
387
+ }
388
+ catch (err) {
389
+ return {
390
+ success: false,
391
+ message: buildFailureSummary(err.message, { stepId: 'checkoff' }),
392
+ };
393
+ }
394
+ }
395
+ // ============================================================================
365
396
  // Preflight warning interactive resolver
366
397
  // ============================================================================
367
398
  function isInteractive() {
@@ -461,6 +492,7 @@ const epicCommand = {
461
492
  { command: 'flo epic run 42', description: 'Explicit run subcommand (same as above)' },
462
493
  { command: 'flo epic status 42', description: 'Check epic progress' },
463
494
  { command: 'flo epic reset 42', description: 'Reset epic state for re-run' },
495
+ { command: 'flo epic checkoff 42 43', description: 'Check story #43 off in epic #42; close #42 if it was the last story' },
464
496
  ],
465
497
  action: async (ctx) => {
466
498
  const subcommand = ctx.args?.[0];
@@ -469,10 +501,11 @@ const epicCommand = {
469
501
  console.log(' flo epic <command> [args] [flags]');
470
502
  console.log('');
471
503
  console.log('Commands:');
472
- console.log(' <issue-number> Execute epic (shorthand for "run")');
473
- console.log(' run <issue-number> Execute a GitHub epic via spell engine');
474
- console.log(' status <epic-number> Check epic progress');
475
- console.log(' reset <epic-number> Reset epic state for re-run');
504
+ console.log(' <issue-number> Execute epic (shorthand for "run")');
505
+ console.log(' run <issue-number> Execute a GitHub epic via spell engine');
506
+ console.log(' status <epic-number> Check epic progress');
507
+ console.log(' reset <epic-number> Reset epic state for re-run');
508
+ console.log(' checkoff <epic> <story> Check a story off in its epic; close the epic if it was the last');
476
509
  console.log('');
477
510
  console.log('Flags:');
478
511
  console.log(' --strategy <name> single-branch (default) or auto-merge');
@@ -518,8 +551,10 @@ const epicCommand = {
518
551
  return showStatus(commandArgs[0] || '');
519
552
  case 'reset':
520
553
  return resetEpic(commandArgs[0] || '');
554
+ case 'checkoff':
555
+ return checkOffStory(commandArgs[0] || '', commandArgs[1] || '');
521
556
  default:
522
- return { success: false, message: `Unknown subcommand: ${subcommand}. Available: run, status, reset` };
557
+ return { success: false, message: `Unknown subcommand: ${subcommand}. Available: run, status, reset, checkoff` };
523
558
  }
524
559
  },
525
560
  };
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Story #195: Shared epic detection & extraction module.
8
8
  */
9
- import { exec } from 'node:child_process';
9
+ import { exec, spawnSync } from 'node:child_process';
10
10
  import { promisify } from 'node:util';
11
11
  const execAsync = promisify(exec);
12
12
  // ============================================================================
@@ -138,4 +138,59 @@ export async function findPrForIssue(issueNumber) {
138
138
  return null;
139
139
  }
140
140
  }
141
+ // ============================================================================
142
+ // Story checkoff (standalone `/flo <story>` runs)
143
+ // ============================================================================
144
+ /**
145
+ * Pure core of the story-checkoff operation — no I/O, unit-testable.
146
+ *
147
+ * Flips a story's `- [ ] #<n>` checkbox to `- [x] #<n>` in an epic body and
148
+ * reports whether the epic is now complete (it has story checkboxes and none
149
+ * remain unchecked). Word-boundary guarded so #12 never matches inside #123.
150
+ */
151
+ export function computeEpicCheckoff(body, storyNumber) {
152
+ const pattern = new RegExp(`- \\[ \\] #${storyNumber}(?!\\d)`);
153
+ const updated = body.replace(pattern, `- [x] #${storyNumber}`);
154
+ const checked = updated !== body;
155
+ const hasStoryCheckboxes = /- \[[ x]\] #\d+/.test(updated);
156
+ const hasUnchecked = /- \[ \] #\d+/.test(updated);
157
+ const allComplete = hasStoryCheckboxes && !hasUnchecked;
158
+ return { updated, checked, allComplete };
159
+ }
160
+ /**
161
+ * Check a story off in its parent epic's checklist, and close the epic when no
162
+ * unchecked stories remain. Used by standalone `/flo <story>` runs via
163
+ * `flo epic checkoff`; the orchestrated `flo epic run` path does its own
164
+ * checkoff inside the spell (epic-single-branch.yaml).
165
+ *
166
+ * Cross-platform (Rule #1): drives `gh` through `spawnSync` arg arrays (no
167
+ * shell string to escape) and pipes the rewritten body over stdin
168
+ * (`--body-file -`), the same pattern proven on Windows CI in the spell.
169
+ */
170
+ export async function checkOffStoryInEpic(epicNumber, storyNumber) {
171
+ const issue = await fetchEpicIssue(epicNumber);
172
+ const { updated, checked, allComplete } = computeEpicCheckoff(issue.body || '', storyNumber);
173
+ if (checked) {
174
+ const edit = spawnSync('gh', ['issue', 'edit', String(epicNumber), '--body-file', '-'], {
175
+ input: updated,
176
+ encoding: 'utf8',
177
+ });
178
+ if (edit.status !== 0) {
179
+ throw new Error(`gh issue edit failed: ${(edit.stderr || edit.error?.message || 'unknown').toString().trim()}`);
180
+ }
181
+ }
182
+ const alreadyClosed = issue.state.toUpperCase() === 'CLOSED';
183
+ const epicClosed = allComplete && !alreadyClosed;
184
+ if (epicClosed) {
185
+ const close = spawnSync('gh', [
186
+ 'issue', 'close', String(epicNumber),
187
+ '--reason', 'completed',
188
+ '--comment', `All stories complete — closed automatically after story #${storyNumber}.`,
189
+ ], { encoding: 'utf8' });
190
+ if (close.status !== 0) {
191
+ throw new Error(`gh issue close failed: ${(close.stderr || close.error?.message || 'unknown').toString().trim()}`);
192
+ }
193
+ }
194
+ return { checked, epicClosed, alreadyClosed };
195
+ }
141
196
  //# sourceMappingURL=detection.js.map
@@ -3,5 +3,5 @@
3
3
  *
4
4
  * Story #195: Shared epic detection & extraction module.
5
5
  */
6
- export { isEpicIssue, extractStories, extractUncheckedStories, fetchEpicIssue, enrichStoryNames, findPrForIssue, } from './detection.js';
6
+ export { isEpicIssue, extractStories, extractUncheckedStories, fetchEpicIssue, enrichStoryNames, findPrForIssue, computeEpicCheckoff, checkOffStoryInEpic, } from './detection.js';
7
7
  //# sourceMappingURL=index.js.map
@@ -90,6 +90,20 @@ function parseNavigation(metadataJson, mode) {
90
90
  headerLevel: meta.headerLevel,
91
91
  };
92
92
  }
93
+ // #1262 lever #1 (dedup-by-source): collapse multiple representations of the
94
+ // SAME underlying source to one result slot. Dogfooding showed a dense query's
95
+ // top-8 is routinely ~40% duplicates — e.g. one file indexed in code-map as
96
+ // `file:<path>` AND in patterns as `pattern:file:<path>`, or a test surfaced as
97
+ // both `file:<path>` and `test-file:<path>`. Path-bearing keys reduce to their
98
+ // path; symbol/chunk/learning keys keep their own identity (a `pattern:class:X`
99
+ // symbol view is NOT the same source as the file, so it survives separately).
100
+ // Operates purely on logical DB keys (always forward-slash), so platform-agnostic.
101
+ function sourceIdOf(key) {
102
+ const pathMatch = key.match(/^(?:pattern:)?(?:file|test-file|dir):(.+)$/);
103
+ if (pathMatch)
104
+ return `src:${pathMatch[1]}`;
105
+ return key.startsWith('pattern:') ? key.slice('pattern:'.length) : key;
106
+ }
93
107
  function shapeRetrievedEntry(entry) {
94
108
  let value = entry.content;
95
109
  try {
@@ -325,39 +339,53 @@ export const memoryTools = [
325
339
  },
326
340
  {
327
341
  name: 'memory_search',
328
- description: 'Semantic vector search using HNSW index (150x-12,500x faster than keyword search). When a result has a non-null `navigation` crumb, you MUST traverse via `memory_get_neighbors` — bulk `memory_retrieve` per hit is a protocol violation. See `.claude/guidance/moflo-memory-protocol.md`.',
342
+ description: 'Semantic vector search using HNSW index (150x-12,500x faster than keyword search). On chunk hits (non-null `navigation`) you MUST get adjacent context via chunk traversal, never a full-doc `Read`: prefer `expand: "neighbors"` in THIS call (cheapest inlines prev/next), else `memory_get_neighbors`. Bulk `memory_retrieve` per hit is a protocol violation. See `.claude/guidance/moflo-memory-protocol.md`.',
329
343
  category: 'memory',
330
344
  inputSchema: {
331
345
  type: 'object',
332
346
  properties: {
333
347
  query: { type: 'string', description: 'Search query (semantic similarity)' },
334
348
  namespace: { type: 'string', description: 'Namespace to search (default: all namespaces)' },
335
- limit: { type: 'number', description: 'Maximum results (default: 8)' },
349
+ limit: { type: 'number', description: 'Maximum results (default: 8). Multiple representations of the same source (a file indexed in both code-map and patterns, a test surfaced as both file and test-file) are collapsed to one before this cap, so every slot is a distinct source.' },
336
350
  threshold: { type: 'number', description: 'Minimum similarity threshold 0-1 (default: 0.5)' },
351
+ expand: {
352
+ type: 'string',
353
+ enum: ['neighbors'],
354
+ description: "Set to 'neighbors' to inline each chunk hit's adjacent (prev/next) chunk content in this single call. Use instead of a follow-up full-doc `Read` when you need surrounding context. Off by default to keep the envelope lean.",
355
+ },
337
356
  },
338
357
  required: ['query'],
339
358
  },
340
359
  handler: async (input) => {
341
360
  await ensureInitialized();
342
- const { searchEntries } = await getMemoryFunctions();
361
+ const { searchEntries, getEntry } = await getMemoryFunctions();
343
362
  const query = input.query;
344
363
  const namespace = input.namespace || 'all';
345
- // #1053 S6: tighter defaults fewer hits, higher relevance bar.
364
+ // #1053 S6: injected-token bar. Each returned hit is injected verbatim,
365
+ // so keep the cap tight — but #1262 dedup (below) makes every one of these
366
+ // slots a DISTINCT source rather than 8 slots holding ~5 uniques + dupes.
346
367
  const limit = input.limit || 8;
347
368
  // Falsiness check would coerce a caller-supplied 0 to default and silently
348
369
  // filter low-similarity matches; use a typeof guard so explicit zero
349
370
  // means "no threshold" (#837).
350
371
  const threshold = typeof input.threshold === 'number' ? input.threshold : 0.5;
372
+ // #1262 lever #3: opt-in single-call neighbor expansion.
373
+ const wantExpand = input.expand === 'neighbors' || input.expand === true;
374
+ // #1262 lever #1: over-fetch so dedup-by-source can still return a FULL
375
+ // `limit` distinct sources. Local HNSW makes the extra hits free (they're
376
+ // just not injected); 3x headroom clears the ~40% duplication observed in
377
+ // dogfooding. Capped so an explicit large limit can't blow up the fetch.
378
+ const overscan = Math.min(limit * 3, 50);
351
379
  validateMemoryInput(undefined, undefined, query);
352
380
  try {
353
381
  const result = await searchEntries({
354
382
  query,
355
383
  namespace,
356
- limit,
384
+ limit: overscan,
357
385
  threshold,
358
386
  });
359
387
  // Parse JSON values in results
360
- const results = result.results.map(r => {
388
+ const mapped = result.results.map(r => {
361
389
  let value = r.content;
362
390
  try {
363
391
  value = JSON.parse(r.content);
@@ -379,13 +407,67 @@ export const memoryTools = [
379
407
  navigation,
380
408
  };
381
409
  });
410
+ // #1262 lever #1: collapse duplicate representations of the same source,
411
+ // keeping the highest-scoring one (results arrive score-desc), then trim
412
+ // to `limit`. Net effect vs. the old code: `limit` DISTINCT sources
413
+ // instead of `limit` slots that were often ~40% near-duplicates.
414
+ const seenSources = new Set();
415
+ const results = [];
416
+ for (const hit of mapped) {
417
+ const id = sourceIdOf(hit.key);
418
+ if (seenSources.has(id))
419
+ continue;
420
+ seenSources.add(id);
421
+ results.push(hit);
422
+ if (results.length >= limit)
423
+ break;
424
+ }
425
+ // #1262 lever #3: when asked, fetch each chunk hit's prev/next content
426
+ // inline (one round-trip) so the model doesn't fall back to a full-doc
427
+ // `Read` for surrounding context. Opt-in keeps the default lean.
428
+ if (wantExpand) {
429
+ await Promise.all(results.map(async (hit) => {
430
+ const nav = hit.navigation;
431
+ if (!nav)
432
+ return; // non-chunk hit — nothing adjacent to fetch
433
+ const targets = [
434
+ { position: 'prev', key: nav.prevChunk },
435
+ { position: 'next', key: nav.nextChunk },
436
+ ].filter((t) => Boolean(t.key));
437
+ if (targets.length === 0)
438
+ return;
439
+ const fetched = await Promise.all(targets.map(async (t) => {
440
+ const res = await getEntry({ key: t.key, namespace: hit.namespace });
441
+ if (!res.found || !res.entry)
442
+ return null;
443
+ const entry = res.entry;
444
+ let value = entry.content;
445
+ try {
446
+ value = JSON.parse(entry.content);
447
+ }
448
+ catch { /* keep string */ }
449
+ return { position: t.position, key: t.key, value };
450
+ }));
451
+ const neighbors = fetched.filter((n) => n !== null);
452
+ if (neighbors.length > 0)
453
+ hit.expanded = neighbors;
454
+ }));
455
+ }
382
456
  notifyMemoryGate();
457
+ // #1262 lever #3: steer away from full-doc `Read` toward the cheap
458
+ // adjacent-context path — but only when it's actionable (chunk hits
459
+ // present and the caller didn't already expand), so the hint itself
460
+ // costs no tokens on the common case.
461
+ const nextStep = !wantExpand && results.some(r => r.navigation)
462
+ ? "Chunk hits present. For adjacent context, re-call memory_search with expand:'neighbors' (one call) rather than Read-ing parentPath — full-doc Read costs far more tokens."
463
+ : undefined;
383
464
  // #1053 S6: searchTime dropped from MCP envelope (CLI keeps it for
384
465
  // human reading); `backend` retained — doctor reads it (#1053 epic).
385
466
  return {
386
467
  query,
387
468
  results,
388
469
  total: results.length,
470
+ ...(nextStep ? { nextStep } : {}),
389
471
  backend: BACKEND_LABEL,
390
472
  };
391
473
  }
@@ -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.11.6';
5
+ export const VERSION = '4.11.8';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moflo",
3
- "version": "4.11.6",
3
+ "version": "4.11.8",
4
4
  "description": "MoFlo — AI agent orchestration for Claude Code. A standalone, opinionated toolkit with semantic memory, learned routing, gates, spells, and the /flo issue-execution skill.",
5
5
  "main": "dist/src/cli/index.js",
6
6
  "type": "module",
@@ -94,7 +94,7 @@
94
94
  "@typescript-eslint/eslint-plugin": "^7.18.0",
95
95
  "@typescript-eslint/parser": "^7.18.0",
96
96
  "eslint": "^8.0.0",
97
- "moflo": "^4.11.5",
97
+ "moflo": "^4.11.7",
98
98
  "tsx": "^4.21.0",
99
99
  "typescript": "^5.9.3",
100
100
  "vitest": "^4.0.0"