moflo 4.10.11 → 4.10.13
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/guidance/shipped/moflo-core-guidance.md +16 -0
- package/.claude/guidance/shipped/moflo-memory-protocol.md +171 -11
- package/.claude/helpers/gate.cjs +139 -14
- package/.claude/skills/publish/SKILL.md +46 -8
- package/bin/gate.cjs +139 -14
- package/bin/lib/moflo-paths.mjs +74 -4
- package/bin/session-start-launcher.mjs +173 -5
- package/dist/src/cli/commands/doctor-checks-config.js +141 -3
- package/dist/src/cli/commands/doctor-fixes.js +202 -10
- package/dist/src/cli/commands/doctor-registry.js +9 -1
- package/dist/src/cli/commands/init.js +33 -0
- package/dist/src/cli/commands/memory.js +11 -4
- package/dist/src/cli/commands/swarm.js +29 -60
- package/dist/src/cli/init/claudemd-generator.js +6 -2
- package/dist/src/cli/init/helpers-generator.js +23 -3
- package/dist/src/cli/init/moflo-init.js +4 -2
- package/dist/src/cli/init/settings-generator.js +9 -3
- package/dist/src/cli/mcp-server.js +104 -2
- package/dist/src/cli/memory/ewc-consolidation.js +22 -6
- package/dist/src/cli/memory/sona-optimizer.js +25 -7
- package/dist/src/cli/movector/lora-adapter.js +22 -7
- package/dist/src/cli/movector/moe-router.js +22 -6
- package/dist/src/cli/services/hook-block-hash.js +5 -2
- package/dist/src/cli/services/hook-wiring.js +38 -4
- package/dist/src/cli/services/moflo-paths.js +36 -0
- package/dist/src/cli/services/project-root.js +84 -25
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
|
@@ -176,6 +176,21 @@ Checks: Node version (20+), Git, config validity, daemon status, memory database
|
|
|
176
176
|
|
|
177
177
|
---
|
|
178
178
|
|
|
179
|
+
## Monorepo Layout
|
|
180
|
+
|
|
181
|
+
**Purpose:** prevent the most common moflo misconfig — daemon islands in monorepos (#1174).
|
|
182
|
+
|
|
183
|
+
| Rule | Why |
|
|
184
|
+
|------|-----|
|
|
185
|
+
| One `.moflo/` per monorepo, at the repo root | The daemon, MCP server, and CLI all walk up from `cwd` to locate state. Two `.moflo/` directories under one tree means two daemons with separate sockets, ports, and registries — the MCP server bound to one will not see tools/state from the other. |
|
|
186
|
+
| Never run `flo init` inside a sub-workspace of an existing moflo project | `flo init` refuses by default when an ancestor `.moflo/moflo.db` is detected. `--force` overrides if you genuinely want isolated state. |
|
|
187
|
+
| `flo doctor` flags every nested `.moflo/` it finds | Component name: `nested-moflo`. Status warns when any island exists. |
|
|
188
|
+
| `flo doctor --fix -c nested-moflo` archives each nested directory | Renames `<sub>/.moflo` → `<sub>/.moflo-archived-<ISO>` (never deletes). Manual review path: archived directories stay on disk. |
|
|
189
|
+
|
|
190
|
+
The resolver (`findProjectRoot`) prefers the topmost ancestor with `.moflo/moflo.db` so every cwd in the tree agrees on the canonical anchor. If `CLAUDE_PROJECT_DIR` is set explicitly, it overrides this — only use that override when you intend isolated state.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
179
194
|
## Troubleshooting
|
|
180
195
|
|
|
181
196
|
| Symptom | Cause | Fix |
|
|
@@ -188,6 +203,7 @@ Checks: Node version (20+), Git, config validity, daemon status, memory database
|
|
|
188
203
|
| Embeddings fail offline / air-gapped | `fastembed` model cache missing | Pre-populate `~/.cache/fastembed` or set `FASTEMBED_CACHE` (see `docs/modules/embeddings.md`) |
|
|
189
204
|
| `flo` command not found | Not in PATH | Use `npx flo` or `node node_modules/moflo/bin/index-guidance.mjs` |
|
|
190
205
|
| Bundled guidance not indexed | Running inside the moflo repo | Bundled guidance only indexes when installed as a dependency in a different project |
|
|
206
|
+
| `mcp__moflo__*` tools missing in monorepo session | Nested `.moflo/` directories spawned separate daemons (#1174) | Run `flo doctor -c nested-moflo`; if any are found, `flo doctor --fix -c nested-moflo` archives them. Restart Claude Code to reconnect. |
|
|
191
207
|
|
|
192
208
|
See `.claude/guidance/moflo-memory-strategy.md` for memory-specific troubleshooting and `.claude/guidance/moflo-spell-troubleshooting.md` for spell sandbox/network failures.
|
|
193
209
|
|
|
@@ -1,34 +1,194 @@
|
|
|
1
|
-
# MoFlo Memory Protocol —
|
|
1
|
+
# MoFlo Memory Protocol — Pick Namespace, Query, Traverse
|
|
2
2
|
|
|
3
|
-
**Purpose:** How to use moflo's chunked memory effectively.
|
|
3
|
+
**Purpose:** How to use moflo's chunked memory effectively. Memory indexes far more than user-feedback narratives: per-directory symbol tables, test-to-source reverse indexes, RAG-chunked guidance docs, code patterns, and incident learnings. Querying the wrong namespace — or asking the wrong shape of question — wastes the gate-enforced first search. Pick the namespace, pivot on the symbol, read the similarity score, traverse via neighbors.
|
|
4
|
+
|
|
5
|
+
## Rule (MUST)
|
|
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.
|
|
4
8
|
|
|
5
9
|
---
|
|
6
10
|
|
|
7
|
-
##
|
|
11
|
+
## What's Actually in Each Namespace
|
|
12
|
+
|
|
13
|
+
The moflo index is large and dense. Sizing as of 2026-05-16 on the moflo repo (similar shape in any consumer project after first indexing pass):
|
|
14
|
+
|
|
15
|
+
| Namespace | Entries | What's indexed | Use for |
|
|
16
|
+
|-----------|---------|----------------|---------|
|
|
17
|
+
| `code-map` | ~1,400 | Per-directory symbol tables (`dir:src/cli/services` lists ~430 symbols → defining files), per-file type/function summaries | "where is X defined", "what's in directory Y", "what types live in file Z" |
|
|
18
|
+
| `guidance` | ~1,000 | RAG-chunked `.claude/guidance/**/*.md` — every chunk has nav crumbs (parent doc, prev/next/siblings) | Project rules, decision context, "how should I…" docs |
|
|
19
|
+
| `patterns` | ~970 | Per-file pattern entries, aggregate stats ("middleware: 16 occurrences"), narrative fix-patterns (`pattern:swarm-dir-recreation-fix-1168`) | "what's our pattern for X", recurring fix shapes, conventions |
|
|
20
|
+
| `tests` | ~850 | `test-file:` entries per test, plus `test-map:<production path>` reverse-index ("2 test files cover this") | "what tests cover module X", test inventory, coverage gaps |
|
|
21
|
+
| `learnings` | grows over time | Incident narratives keyed by issue/PR (`1145-daemon-port-collision-fix`) | Error → fix recall, "did we hit this before", post-mortems |
|
|
22
|
+
|
|
23
|
+
`code-map` and `tests` are auto-indexed from the source tree. `guidance` is auto-indexed from `.claude/guidance/**`. `patterns` mixes auto-mined heuristics with narratives stored via `memory_store`. `learnings` is curated — store with `mcp__moflo__memory_store` when you finish a non-trivial debug.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Worked Examples — Real Queries Against the Live Store
|
|
28
|
+
|
|
29
|
+
Each example shows the query, namespace, top hit, similarity score, and why memory wins over the alternative.
|
|
30
|
+
|
|
31
|
+
### Example 1 — "Where is symbol X defined" → `code-map`
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
mcp__moflo__memory_search {
|
|
35
|
+
query: "where is BashSafetyHook defined",
|
|
36
|
+
namespace: "code-map"
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Top hits:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
file:src/cli/shared/hooks/safety/bash-safety.ts similarity 0.86
|
|
44
|
+
dir:src/cli/shared/hooks/safety similarity 0.82
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Wins over Grep.** Grep returns every mention of the identifier across the tree; `code-map` returns the defining file plus the sibling-symbol context (the dir entry lists all 18 types in that directory) in one call.
|
|
48
|
+
|
|
49
|
+
### Example 2 — "Tests for module Y" → `tests`
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
mcp__moflo__memory_search {
|
|
53
|
+
query: "tests for daemon port collision",
|
|
54
|
+
namespace: "tests"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Top hits:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
test-file:src/cli/__tests__/commands/daemon-port-resolution.test.ts similarity 0.79
|
|
62
|
+
test-map:src/cli/commands/daemon similarity 0.75
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Wins over Glob + Read.** The `test-map:` reverse-index answers "what tests cover this production path" directly — no need to Glob for filename matches and Read each candidate to confirm coverage.
|
|
66
|
+
|
|
67
|
+
### Example 3 — "Patterns for X" → `patterns`
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
mcp__moflo__memory_search {
|
|
71
|
+
query: "vitest mocking patterns for middleware",
|
|
72
|
+
namespace: "patterns"
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Top hits:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
pattern-import-module-e9oqfm similarity 0.87
|
|
80
|
+
pattern-test-mocking-76mtme similarity 0.81
|
|
81
|
+
pattern-api-middleware-q9gk50 similarity 0.78
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Competitive with Grep + multiple Reads.** Returns conceptual hits without needing to know file paths or naming conventions.
|
|
8
85
|
|
|
9
|
-
|
|
86
|
+
### Example 4 — "How does feature work" → `guidance`
|
|
10
87
|
|
|
11
|
-
|
|
88
|
+
```
|
|
89
|
+
mcp__moflo__memory_search {
|
|
90
|
+
query: "how should subagents handle the memory search gate",
|
|
91
|
+
namespace: "guidance"
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Top hits — four chunks across four different docs, all 0.86+:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
chunk-skill-eldar-2 (parent: doc-skill-eldar)
|
|
99
|
+
chunk-guidance-upgrade-contract-6 (parent: doc-guidance-upgrade-contract)
|
|
100
|
+
chunk-guidance-memory-traversal-architecture-5 (parent: doc-guidance-memory-traversal-architecture)
|
|
101
|
+
chunk-guidance-moflo-subagents-0 (parent: doc-guidance-moflo-subagents)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Wins over Read.** Semantic recall across docs without knowing filenames. Each hit carries a `navigation` object — traverse to siblings/prev/next for adjacent context.
|
|
105
|
+
|
|
106
|
+
### Example 5 — "Did we hit this error before" → `learnings`
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
mcp__moflo__memory_search {
|
|
110
|
+
query: "daemon port collision fix",
|
|
111
|
+
namespace: "learnings"
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Hits a curated incident narrative (e.g. `1145-daemon-port-collision-fix`) with the prior diagnosis + fix shape. **Wins over searching closed GitHub issues** — the narrative is the distilled fix, not the raw discussion.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Querying Technique — Five Rules for High-Signal Searches
|
|
120
|
+
|
|
121
|
+
Five rules that turn ceremonial searches into high-signal ones. Each one fixes a real failure mode observed in long sessions.
|
|
122
|
+
|
|
123
|
+
| Rule | Why |
|
|
124
|
+
|------|-----|
|
|
125
|
+
| **Pivot on the symbol.** Include the bare identifier (`BashSafetyHook`, `findProjectRoot`) verbatim in the query | Embedding similarity is much higher on the literal token than on a natural-language paraphrase |
|
|
126
|
+
| **Always specify `namespace`** when you know the answer shape | Cross-namespace results dilute relevance — a `tests` query without namespace pulls noisy `code-map` and `patterns` hits |
|
|
127
|
+
| **Read the `similarity` score** | `≥ 0.80` is a confident hit; `0.60–0.79` is tangential; `< 0.60` is noise. Re-query with a sharper symbol/keyword rather than acting on weak hits |
|
|
128
|
+
| **Traverse, don't re-search** | When a chunk hit has `navigation`, use `mcp__moflo__memory_get_neighbors` for adjacent context — one round-trip, no re-embedding cost |
|
|
129
|
+
| **Don't bulk-retrieve search hits** | `memory_retrieve` is for one specific key you already hold. Calling it for every search hit defeats the chunking architecture |
|
|
130
|
+
|
|
131
|
+
### Query shape — do / don't
|
|
132
|
+
|
|
133
|
+
| Don't | Do |
|
|
134
|
+
|-------|-----|
|
|
135
|
+
| `memory_search { query: "where is the bash safety hook implemented in the project" }` | `memory_search { query: "BashSafetyHook", namespace: "code-map" }` |
|
|
136
|
+
| `memory_search { query: "find all tests for daemon stuff" }` | `memory_search { query: "daemon port collision", namespace: "tests" }` |
|
|
137
|
+
| `memory_search { query: "how do I do mocks" }` (no namespace, vague) | `memory_search { query: "vitest mocking middleware", namespace: "patterns" }` |
|
|
138
|
+
| `memory_retrieve` on each of 5 search hits | `memory_get_neighbors { key: <best hit>, include: ['prev','next','siblings'] }` |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Tool Selection — Memory API
|
|
143
|
+
|
|
144
|
+
Once you have a search hit, pick the right follow-up call by what you need next. Bulk-retrieving every search hit is a protocol violation; the table below maps each follow-up shape to its single correct tool.
|
|
12
145
|
|
|
13
146
|
| You want | Use | Why |
|
|
14
147
|
|----------|-----|-----|
|
|
15
|
-
| Find an entry-point | `mcp__moflo__memory_search` | Returns chunk hits with
|
|
16
|
-
| Adjacent context (1 chunk over) | `mcp__moflo__memory_get_neighbors` `{ key, include: ['prev','next'] }` | One round-trip,
|
|
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 |
|
|
17
150
|
| Same-section peers (h2/h3 family) | `memory_get_neighbors` `{ include: ['siblings'] }` or `['parent','children']` | Hierarchical traversal — cheaper than re-searching |
|
|
18
|
-
| Full content of one chunk | `mcp__moflo__memory_retrieve` `{ key }` |
|
|
151
|
+
| Full content of one specific chunk | `mcp__moflo__memory_retrieve` `{ key }` | For a key you already hold — never for bulk-retrieving search hits |
|
|
19
152
|
| Whole source doc when truly needed | `Read` `parentPath` from any chunk's nav | Disk read is cheaper than re-indexed `doc-*` |
|
|
20
153
|
|
|
21
|
-
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Tool Selection — Memory vs. Filesystem Tools
|
|
157
|
+
|
|
158
|
+
Pick memory when the question is about indexed knowledge (symbols, tests, patterns, docs, incidents); pick filesystem/git when the question is about *current authoritative state* the index can't guarantee fresher than disk.
|
|
159
|
+
|
|
160
|
+
| Question shape | First call |
|
|
161
|
+
|----------------|-----------|
|
|
162
|
+
| "Where is symbol X defined" | `memory_search` namespace `code-map` (pivot on bare symbol) |
|
|
163
|
+
| "What tests cover module Y" | `memory_search` namespace `tests` |
|
|
164
|
+
| "What's our pattern for Z" | `memory_search` namespace `patterns` |
|
|
165
|
+
| "How / why / project rule for W" | `memory_search` namespace `guidance` |
|
|
166
|
+
| "Did we hit error V before" | `memory_search` namespace `learnings` |
|
|
167
|
+
| "Find files matching glob `**/*.test.ts`" | `Glob` — file-system metadata, not indexed content |
|
|
168
|
+
| "What's in the working tree right now / what did I just edit" | `Read` — memory is a snapshot, working-tree is authoritative |
|
|
169
|
+
| "What's in this commit / git history" | `git log` / `git diff` |
|
|
170
|
+
|
|
171
|
+
The first five rows pivot through memory because the index already knows the answer. The last three pivot through filesystem/git because they ask about *current authoritative state*, which memory cannot guarantee fresher than disk.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Anti-Patterns — Common Memory Query Mistakes
|
|
176
|
+
|
|
177
|
+
These six failure modes account for almost every "memory returned noise" complaint. Each row pairs the antipattern with the corrective shape from the cheat sheet above.
|
|
22
178
|
|
|
23
179
|
| Don't | Do instead |
|
|
24
180
|
|-------|-----------|
|
|
181
|
+
| Search memory without a namespace, get noise, fall back to Grep | Specify the namespace that matches the question shape (table above) |
|
|
25
182
|
| Retrieve every search hit blindly | Traverse via `memory_get_neighbors` when `navigation` is present — bulk `memory_retrieve` per hit is a protocol violation |
|
|
26
183
|
| Open the source file when a chunk would do | Stay in the chunk graph; `Read` `parentPath` only for the rare full-doc case |
|
|
27
184
|
| Search again for a key you already have | `memory_retrieve` or `memory_get_neighbors` directly |
|
|
185
|
+
| Phrase queries as natural-language questions | Pivot on the bare symbol or keyword — embedding similarity is higher on the literal token |
|
|
186
|
+
| Act on a hit with similarity < 0.6 | Re-query with a sharper symbol/keyword; weak hits are noise, not signal |
|
|
28
187
|
|
|
29
188
|
---
|
|
30
189
|
|
|
31
190
|
## See Also
|
|
32
191
|
|
|
33
|
-
- `.claude/guidance/moflo-agent-rules.md` § Memory-First Protocol —
|
|
34
|
-
- `.claude/guidance/moflo-memory-strategy.md` — How chunking, embeddings, and the RAG index work
|
|
192
|
+
- `.claude/guidance/moflo-agent-rules.md` § Memory-First Protocol — gate enforcement, namespace selection rules
|
|
193
|
+
- `.claude/guidance/moflo-memory-strategy.md` — How chunking, embeddings, and the RAG index work under the hood
|
|
194
|
+
- `.claude/guidance/moflo-memorydb-maintenance.md` — How the underlying memory DB is kept healthy (indexing, vacuum, integrity)
|
package/.claude/helpers/gate.cjs
CHANGED
|
@@ -81,7 +81,21 @@ var config = loadGateConfig();
|
|
|
81
81
|
var command = process.argv[2];
|
|
82
82
|
|
|
83
83
|
var EXEMPT = ['.claude/', '.claude\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state', 'node_modules', 'moflo.yaml'];
|
|
84
|
-
|
|
84
|
+
// #1171 — DANGEROUS gained PowerShell additions to match the matcher widening
|
|
85
|
+
// that now routes the dedicated `PowerShell` tool through check-dangerous-command.
|
|
86
|
+
// POSIX entries still apply because PS will execute them when invoked. Substring
|
|
87
|
+
// match (case-insensitive) inside the gate.
|
|
88
|
+
var DANGEROUS = [
|
|
89
|
+
'rm -rf /', 'format c:', 'del /s /q c:\\', ':(){:|:&};:', 'mkfs.', '> /dev/sda',
|
|
90
|
+
// PowerShell destructive patterns. Won't catch every adversarial spelling
|
|
91
|
+
// (PS aliases let `ri -r -force C:\` mean the same thing) but covers the
|
|
92
|
+
// common-typo destruction class — symmetric to the POSIX list's intent.
|
|
93
|
+
'remove-item -recurse -force c:\\',
|
|
94
|
+
'remove-item -recurse -force /',
|
|
95
|
+
'remove-item -recurse -force ~',
|
|
96
|
+
'format-volume',
|
|
97
|
+
'clear-disk',
|
|
98
|
+
];
|
|
85
99
|
|
|
86
100
|
// #1132 — Bash memory-first gate.
|
|
87
101
|
//
|
|
@@ -94,6 +108,9 @@ var CREDIT_MEMORY_SEARCH_RE = /semantic-search|memory search|memory retrieve|mem
|
|
|
94
108
|
// check-before-scan gates by going through the shell. Anchored to the start of
|
|
95
109
|
// the line so subcommands inside pipelines or `npm install grep` don't trip.
|
|
96
110
|
// Covers POSIX read/search tools, Windows cmd `type`, and PowerShell readers.
|
|
111
|
+
// #1171 — extended with PowerShell-native exploration forms now that the matcher
|
|
112
|
+
// widens to the `PowerShell` tool. Plain `Get-ChildItem` without -Recurse stays
|
|
113
|
+
// uncovered (it's `ls`-equivalent and plain `ls` is allowed).
|
|
97
114
|
var READ_LIKE_BASH_RE = new RegExp([
|
|
98
115
|
'^\\s*(?:cat|head|tail|less|more|bat|xxd|od|hexdump)\\b',
|
|
99
116
|
'^\\s*(?:grep|rg|ag|fgrep|egrep|find|fd)\\b',
|
|
@@ -108,6 +125,17 @@ var READ_LIKE_BASH_RE = new RegExp([
|
|
|
108
125
|
// primary risk pattern is leaking past the gate via `type src\foo.ts`.
|
|
109
126
|
'^\\s*type\\s+\\S*[\\\\/.]',
|
|
110
127
|
'^\\s*(?:Get-Content|gc|Select-String|sls)\\b',
|
|
128
|
+
// #1171 — PowerShell recursive exploration (parallel to POSIX `find`/`fd`).
|
|
129
|
+
// The `-Recurse` flag is what makes it expensive enough to gate; plain
|
|
130
|
+
// `Get-ChildItem` is `ls`-shaped and intentionally not blocked.
|
|
131
|
+
'^\\s*(?:Get-ChildItem|gci)\\b[^|]*-Recurse\\b',
|
|
132
|
+
// #1171 — cmd-style recursive listing (`dir /s` or `dir /S`). Only the
|
|
133
|
+
// Windows `/s` form, NOT POSIX `dir -s` (sort-by-size, where `dir` is aliased
|
|
134
|
+
// to `ls -l` on many distros) — false-positive blocking that would break
|
|
135
|
+
// legitimate POSIX listings.
|
|
136
|
+
'^\\s*dir\\b[^|]*\\s\\/[sS]\\b',
|
|
137
|
+
// #1171 — PowerShell hex dump, parallel to POSIX `xxd`/`hexdump`.
|
|
138
|
+
'^\\s*Format-Hex\\b',
|
|
111
139
|
].join('|'), 'i');
|
|
112
140
|
// CARVE-OUT: commands that LOOK read-like but are operational. Anchored to the
|
|
113
141
|
// LEADING command — the pipe-filter case (`npm test | grep FAIL`) is already
|
|
@@ -129,6 +157,32 @@ var BASH_CARVE_OUT_RE = new RegExp([
|
|
|
129
157
|
// `find` commands that lack a `-delete` / `-exec rm` suffix.
|
|
130
158
|
'^\\s*find\\s+.+?-(delete|exec\\s+rm)\\b',
|
|
131
159
|
].join('|'));
|
|
160
|
+
// #1171 follow-up — strip quoted string bodies and heredoc bodies from a shell
|
|
161
|
+
// command for purposes of dangerous-pattern substring matching. Used by
|
|
162
|
+
// check-dangerous-command. Does NOT strip $(...) or `...` because those bodies
|
|
163
|
+
// execute. Double-quoted strings handle escaped quotes (`\"`) correctly so
|
|
164
|
+
// `git commit -m "fix \"X\""` strips the whole quoted body, not just the first
|
|
165
|
+
// `\"` pair. Single quotes don't have escapes in bash/sh — `'[^']*'` is exact.
|
|
166
|
+
function stripQuotedAndHeredocs(cmd) {
|
|
167
|
+
var out = cmd;
|
|
168
|
+
// Heredoc tail: `<<TOKEN`, `<<-TOKEN`, `<<'TOKEN'`, `<<"TOKEN"` through end-of-input.
|
|
169
|
+
// Bash heredocs are multi-line; in single-line tool inputs they show up as the
|
|
170
|
+
// tail after `<<TOKEN`. Conservative tail-strip — benign content after a heredoc
|
|
171
|
+
// body on the same logical line is also stripped, harmless for this gate.
|
|
172
|
+
// Token class includes `-` so hyphenated heredoc tags (`<<END-OF-DOC`) match
|
|
173
|
+
// the full token, not just the leading word — without this the strip would
|
|
174
|
+
// halt at `<<END` and leave `-OF-DOC` plus the body as literal text.
|
|
175
|
+
out = out.replace(/<<-?\s*['"]?[\w-]+['"]?[\s\S]*$/, '');
|
|
176
|
+
// Here-string `<<<word` — strip the word.
|
|
177
|
+
out = out.replace(/<<<\s*\S+/g, '');
|
|
178
|
+
// Single-quoted strings — no escapes inside single quotes in sh/bash.
|
|
179
|
+
out = out.replace(/'[^']*'/g, "''");
|
|
180
|
+
// Double-quoted strings — `(?:[^"\\]|\\.)*` matches anything except an
|
|
181
|
+
// unescaped `"`, so escaped `\"` mid-string doesn't terminate the strip early.
|
|
182
|
+
out = out.replace(/"(?:[^"\\]|\\.)*"/g, '""');
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
|
|
132
186
|
var DIRECTIVE_RE = /^(yes|no|yeah|yep|nope|sure|ok|okay|correct|right|exactly|perfect)\b/i;
|
|
133
187
|
var TASK_RE = /\b(fix|bug|error|implement|add|create|build|write|refactor|debug|test|feature|issue|security|optimi)\b/i;
|
|
134
188
|
|
|
@@ -254,14 +308,27 @@ var TEST_RUNNER_RE = /(?:^|[^a-z])(?:npm|yarn|pnpm|bun)\s+(?:run\s+)?(?:test|t)(
|
|
|
254
308
|
// Edits to these don't change runtime behaviour, so they don't invalidate prior test/simplify runs.
|
|
255
309
|
// Lock files and .gitignore are tracked but inert; package.json/*.yaml ARE source — they reset.
|
|
256
310
|
var EDIT_RESET_SKIP_BOTH_RE = /\.(md|markdown|txt|rst|adoc|lock|gitignore)$|(?:^|[\\\/])(CHANGELOG(?:\.md)?|\.env\.example|package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb)$/i;
|
|
311
|
+
// #1176 — path-based inert markers. The extension-based RE above can't cover
|
|
312
|
+
// `.github/workflows/*.yml` without also exempting `moflo.yaml` / `tsconfig.yaml`
|
|
313
|
+
// (which ARE source). Anchor on the GitHub-meta directories that hold CI config
|
|
314
|
+
// and template scaffolds — editing those doesn't expose new runtime surface, so
|
|
315
|
+
// they shouldn't reset testsRun/simplifyRun the way a real source edit does.
|
|
316
|
+
// Trailing terminator includes `.` so the single-file template form
|
|
317
|
+
// `.github/PULL_REQUEST_TEMPLATE.md` matches alongside the directory form.
|
|
318
|
+
var EDIT_RESET_SKIP_PATH_RE = /(?:^|[\\\/])\.github[\\\/](?:workflows|ISSUE_TEMPLATE|PULL_REQUEST_TEMPLATE)(?:[\\\/.]|$)/i;
|
|
257
319
|
// Test files: invalidate the testing gate (tests are stale once test code changes)
|
|
258
320
|
// but NOT the simplify gate — /simplify already reviewed the production code; touching
|
|
259
321
|
// a test file or fixture doesn't expose new untested surface for code review (#908).
|
|
260
322
|
var EDIT_RESET_SKIP_SIMPLIFY_ONLY_RE = /(?:^|[\\\/])(__tests__|__mocks__|tests?|spec|specs|cypress|e2e|fixtures?)[\\\/]|\.(test|spec)\.[mc]?[jt]sx?$|\.fixture\.[mc]?[jt]sx?$/i;
|
|
323
|
+
// #1176 — source-file extensions used by the no-source-files PR exemption.
|
|
324
|
+
// When the cumulative branch diff has zero files matching this RE (i.e. only
|
|
325
|
+
// YAML/MD/JSON/lockfiles/images/templates), the testing/simplify/learnings
|
|
326
|
+
// gates auto-pass at `check-before-pr`. Lists every language moflo ships
|
|
327
|
+
// against — additions here should match TEST_RUNNER_RE's language coverage.
|
|
328
|
+
var SOURCE_FILE_RE = /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|rb|java|kt|swift|c|cc|cpp|h|hpp|sh|bash|ps1)$/i;
|
|
261
329
|
// Docs-only PR exemption: text/markup/image extensions that cannot change runtime behaviour.
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
// on purpose — those are inert for edit-reset (above) but not "documentation".
|
|
330
|
+
// Retained for the transparency message when the diff is *purely* docs (no YAML/JSON either)
|
|
331
|
+
// — gives a more specific reason than "no source files" in that subset.
|
|
265
332
|
var DOCS_ONLY_RE = /\.(md|markdown|txt|rst|adoc|html?|pdf|png|jpe?g|gif|svg|webp|ico|bmp)$/i;
|
|
266
333
|
|
|
267
334
|
// Classifier-aware simplify gate skip. Returns a string reason if the gate
|
|
@@ -285,13 +352,24 @@ function classifyForGateSkip(state) {
|
|
|
285
352
|
} catch (e) { return null; }
|
|
286
353
|
if (typeof classify !== 'function') return null;
|
|
287
354
|
|
|
288
|
-
function tryClassify(diffText, label) {
|
|
355
|
+
function tryClassify(diffText, label, allowSmallReviewFix) {
|
|
289
356
|
try {
|
|
290
357
|
var dec = classify(diffText);
|
|
291
358
|
if (dec.tier === 'TRIVIAL') {
|
|
292
359
|
var loc = (dec.stats.added || 0) + (dec.stats.deleted || 0);
|
|
293
360
|
return label + ' is TRIVIAL (' + loc + ' LOC, ' + (dec.stats.fileCount || 0) + ' file(s))';
|
|
294
361
|
}
|
|
362
|
+
// #1176 — SMALL review-fix shape (snapshot path only). A ≤30-LOC delta with
|
|
363
|
+
// zero new declarations on top of an already-reviewed branch is the typical
|
|
364
|
+
// "apply 3 review fixes" cycle — re-running /flo-simplify against the same
|
|
365
|
+
// surface plus a few-line tweak adds no new signal. Baseline path stays
|
|
366
|
+
// TRIVIAL-only so brand-new SMALL features still get reviewed.
|
|
367
|
+
if (allowSmallReviewFix && dec.tier === 'SMALL') {
|
|
368
|
+
var totalLoc = (dec.stats.added || 0) + (dec.stats.deleted || 0);
|
|
369
|
+
if (totalLoc <= 30 && (dec.stats.declAdded || 0) === 0) {
|
|
370
|
+
return label + ' is SMALL review-fix shape (' + totalLoc + ' LOC, no new declarations)';
|
|
371
|
+
}
|
|
372
|
+
}
|
|
295
373
|
} catch (e) { /* fall through */ }
|
|
296
374
|
return null;
|
|
297
375
|
}
|
|
@@ -311,7 +389,9 @@ function classifyForGateSkip(state) {
|
|
|
311
389
|
var workTreeA = gitDiff(['diff', 'HEAD']) || '';
|
|
312
390
|
if (snapDiff !== null) {
|
|
313
391
|
var combined = snapDiff + (workTreeA ? '\n' + workTreeA : '');
|
|
314
|
-
|
|
392
|
+
// Snapshot path: allow SMALL review-fix shape because the original /simplify
|
|
393
|
+
// already covered the surface and only tiny no-decl-touching tweaks followed.
|
|
394
|
+
var hit = tryClassify(combined, 'delta since last /simplify', true);
|
|
315
395
|
if (hit) return hit;
|
|
316
396
|
}
|
|
317
397
|
}
|
|
@@ -475,6 +555,11 @@ switch (command) {
|
|
|
475
555
|
// #1132 — preserve CREDIT side-effect AND add a BLOCK arm for read-like
|
|
476
556
|
// Bash commands. Wired as PreToolUse[Bash] (was PostToolUse before #1132)
|
|
477
557
|
// so process.exit(2) actually prevents the read from reaching the shell.
|
|
558
|
+
//
|
|
559
|
+
// #1171 — the case name is historical. The matcher now also covers the
|
|
560
|
+
// dedicated `PowerShell` tool, and READ_LIKE_BASH_RE already matched PS
|
|
561
|
+
// readers (Get-Content/Select-String/Get-ChildItem -Recurse/Format-Hex).
|
|
562
|
+
// Treat this case as shell-agnostic read-gate logic.
|
|
478
563
|
var cmd = process.env.TOOL_INPUT_command || '';
|
|
479
564
|
|
|
480
565
|
// 1) CREDIT — preserved behavior. A real memory-search invocation flips
|
|
@@ -531,6 +616,15 @@ switch (command) {
|
|
|
531
616
|
s.testsRun = true;
|
|
532
617
|
writeState(s);
|
|
533
618
|
}
|
|
619
|
+
} else if (cmd) {
|
|
620
|
+
// #1176 — emit a stderr crumb when invoked with a non-empty command that
|
|
621
|
+
// doesn't match the test-runner pattern. Common pitfall: users run the
|
|
622
|
+
// stamp manually from a terminal to "satisfy the gate"; the silent no-op
|
|
623
|
+
// looks indistinguishable from success. gate-hook.mjs drops stderr from
|
|
624
|
+
// exit-0 invocations, so this only surfaces to direct CLI use — exactly
|
|
625
|
+
// the case where the friction lives.
|
|
626
|
+
var preview = cmd.length > 80 ? cmd.slice(0, 77) + '...' : cmd;
|
|
627
|
+
process.stderr.write('gate: record-test-run no-op — TOOL_INPUT_command="' + preview + '" did not match TEST_RUNNER_RE\n');
|
|
534
628
|
}
|
|
535
629
|
break;
|
|
536
630
|
}
|
|
@@ -551,13 +645,21 @@ switch (command) {
|
|
|
551
645
|
if (sha && s.simplifySnapshotSha !== sha) { s.simplifySnapshotSha = sha; changed = true; }
|
|
552
646
|
} catch (e) { /* no git or detached state — skip snapshot, gate still works */ }
|
|
553
647
|
if (changed) writeState(s);
|
|
648
|
+
} else if (skName) {
|
|
649
|
+
// #1176 — same rationale as record-test-run. A no-op stamp on a non-simplify
|
|
650
|
+
// skill name is silent to hooks (gate-hook.mjs drops exit-0 stderr) but
|
|
651
|
+
// visible when a user runs the stamp directly to "satisfy the gate" and
|
|
652
|
+
// wonders why simplifyRun stays false.
|
|
653
|
+
process.stderr.write('gate: record-skill-run no-op — TOOL_INPUT_skill="' + skName + '" is not simplify/flo-simplify\n');
|
|
554
654
|
}
|
|
555
655
|
break;
|
|
556
656
|
}
|
|
557
657
|
case 'reset-edit-gates': {
|
|
558
658
|
var fp = process.env.TOOL_INPUT_file_path || '';
|
|
559
|
-
// Inert files (markdown, lockfiles, CHANGELOG, .env.example)
|
|
560
|
-
|
|
659
|
+
// Inert files (markdown, lockfiles, CHANGELOG, .env.example) AND inert paths
|
|
660
|
+
// (.github/workflows/, .github/ISSUE_TEMPLATE/, .github/PULL_REQUEST_TEMPLATE/, #1176):
|
|
661
|
+
// no gate reset — editing these doesn't expose new runtime surface.
|
|
662
|
+
if (fp && (EDIT_RESET_SKIP_BOTH_RE.test(fp) || EDIT_RESET_SKIP_PATH_RE.test(fp))) break;
|
|
561
663
|
var s = readState();
|
|
562
664
|
// Test-only edits invalidate testsRun but preserve simplifyRun (#908).
|
|
563
665
|
var isTestOnly = fp && EDIT_RESET_SKIP_SIMPLIFY_ONLY_RE.test(fp);
|
|
@@ -580,14 +682,27 @@ switch (command) {
|
|
|
580
682
|
// optional ENV=val prefix segment catches `GH_TOKEN=x gh pr create`.
|
|
581
683
|
var cmd = process.env.TOOL_INPUT_command || '';
|
|
582
684
|
if (!/(?:^|&&\s*|\|\|\s*|;\s*)\s*(?:[A-Z_][A-Z0-9_]*=\S+\s+)*gh\s+pr\s+create\b/.test(cmd)) break;
|
|
583
|
-
//
|
|
584
|
-
// file
|
|
685
|
+
// No-source-files exemption (#1176, supersedes the original docs-only path).
|
|
686
|
+
// If every file changed vs the merge-base is either a docs/image file or a
|
|
687
|
+
// path-inert file (.github/workflows/, ISSUE_TEMPLATE/, PULL_REQUEST_TEMPLATE/)
|
|
688
|
+
// — i.e. NO source files in the diff — skip testing/simplify/learnings gates
|
|
585
689
|
// and surface a one-line transparency note. Falls through to the standard gate
|
|
586
690
|
// on any failure (no base, no diff, exec error) — fail-safe by design.
|
|
691
|
+
//
|
|
692
|
+
// Source-file detection is the inverse of the inert checks: a file is "source"
|
|
693
|
+
// when it matches SOURCE_FILE_RE AND is not inside an inert path. This catches
|
|
694
|
+
// `.github/workflows/foo.sh` (sh extension but path-inert → no source).
|
|
587
695
|
var changed = getChangedFilesVsBase();
|
|
588
|
-
if (changed && changed.length > 0
|
|
589
|
-
|
|
590
|
-
|
|
696
|
+
if (changed && changed.length > 0) {
|
|
697
|
+
var hasSource = changed.some(function(f) {
|
|
698
|
+
return SOURCE_FILE_RE.test(f) && !EDIT_RESET_SKIP_PATH_RE.test(f);
|
|
699
|
+
});
|
|
700
|
+
if (!hasSource) {
|
|
701
|
+
var allDocs = changed.every(function(f) { return DOCS_ONLY_RE.test(f); });
|
|
702
|
+
var reason = allDocs ? 'Docs-only' : 'No source files in branch diff';
|
|
703
|
+
process.stdout.write(reason + ' (' + changed.length + ' file' + (changed.length === 1 ? '' : 's') + ') — skipping testing/simplify/learnings gates.\n');
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
591
706
|
}
|
|
592
707
|
var s = readState();
|
|
593
708
|
// Classifier-aware skip: if delta-since-snapshot or whole-branch diff is
|
|
@@ -620,7 +735,17 @@ switch (command) {
|
|
|
620
735
|
process.exit(2);
|
|
621
736
|
}
|
|
622
737
|
case 'check-dangerous-command': {
|
|
623
|
-
|
|
738
|
+
// #1171 follow-up — strip quoted string bodies and heredoc bodies before
|
|
739
|
+
// substring-matching DANGEROUS. Without this, `git commit -m "...remove-item
|
|
740
|
+
// -recurse -force c:\..."` blocks because the literal pattern appears in
|
|
741
|
+
// the quoted message body. Quoted text isn't executing — the gate's job is
|
|
742
|
+
// to catch typo-class destruction in the actual command, not text mentions
|
|
743
|
+
// inside arguments. Trade-off: `bash -c "rm -rf /"` also bypasses now; the
|
|
744
|
+
// gate is a typo safety net, not a security boundary, so this is acceptable.
|
|
745
|
+
// Command substitutions `$(...)` and backticks are NOT stripped — those
|
|
746
|
+
// bodies execute and dangerous content there is real.
|
|
747
|
+
var raw = process.env.TOOL_INPUT_command || '';
|
|
748
|
+
var cmd = stripQuotedAndHeredocs(raw).toLowerCase();
|
|
624
749
|
for (var i = 0; i < DANGEROUS.length; i++) {
|
|
625
750
|
if (cmd.indexOf(DANGEROUS[i]) >= 0) {
|
|
626
751
|
console.log('[BLOCKED] Dangerous command: ' + DANGEROUS[i]);
|
|
@@ -25,9 +25,9 @@ Automated release pipeline for moflo. Bumps version, commits, builds, tests, run
|
|
|
25
25
|
|
|
26
26
|
## --check / -ch flag (presence-only)
|
|
27
27
|
|
|
28
|
-
**Default (flag absent):** runs build + doctor + trigger-based manual checks only. Skips lint/test/smoke because
|
|
28
|
+
**Default (flag absent):** runs build + doctor + trigger-based manual checks only. Skips local lint/test/smoke because lint+test are covered by `ci.yml` on every PR + push to main, and **cross-platform smoke is dispatched as part of this skill** (Step 8.5 below) — the `release-smoke.yml` workflow runs the full 3-OS matrix on the exact commit about to be published.
|
|
29
29
|
|
|
30
|
-
**With `--check` or `-ch` (any presence):** runs the full pre-flight from `pre-publish-rules.md` — lint, build, test, doctor (strict), clean smoke, populated smoke, and a forced full walk of every manual gate. Use this for publishes that didn't go through a green PR, risky releases, or when you want belt-and-suspenders.
|
|
30
|
+
**With `--check` or `-ch` (any presence):** runs the full pre-flight from `pre-publish-rules.md` — lint, build, test, doctor (strict), local clean smoke, local populated smoke, and a forced full walk of every manual gate. Use this for publishes that didn't go through a green PR, risky releases, or when you want belt-and-suspenders on the local box in addition to the CI matrix.
|
|
31
31
|
|
|
32
32
|
The flag is presence-only. `--check` and `-ch` both set it to true. There is no `--check=true` / `--check=false` syntax — absence means false.
|
|
33
33
|
|
|
@@ -103,14 +103,14 @@ Doctor is the only check with no CI equivalent — it inspects local state (daem
|
|
|
103
103
|
|
|
104
104
|
**Never `--fix` on the publish path.** A release pipeline must fail fast on broken local state, not silently repair it; a doctor that auto-repairs masks the very signal we want — "something is off, stop and investigate before shipping." If `doctor --strict` fails, stop and run `flo healer --fix` (or `npx moflo doctor --fix`) interactively, verify the repair, then retry the publish.
|
|
105
105
|
|
|
106
|
-
### Step 6: Smoke Tests (only if `CHECK_MODE=true`)
|
|
106
|
+
### Step 6: Local Smoke Tests (only if `CHECK_MODE=true`)
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
109
|
npm run test:smoke
|
|
110
110
|
npm run test:smoke:populated
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
Skipped by default
|
|
113
|
+
Skipped by default. Local smoke covers the same harness as CI's Ubuntu leg, so it adds little signal over `release-smoke.yml`'s Ubuntu matrix entry (Step 8.5). Run only when `--check` is set — useful if you want belt-and-suspenders on the local box before dispatching the full CI matrix.
|
|
114
114
|
|
|
115
115
|
### Step 7: Manual Gate Walk (trigger-based, even in default mode)
|
|
116
116
|
|
|
@@ -144,6 +144,42 @@ git push origin main
|
|
|
144
144
|
|
|
145
145
|
Only commit version-related files. Do not stage unrelated changes.
|
|
146
146
|
|
|
147
|
+
### Step 8.5: Dispatch release-smoke and block until green
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Capture the SHA we just pushed — release-smoke runs against this exact commit.
|
|
151
|
+
PUBLISH_SHA=$(git rev-parse HEAD)
|
|
152
|
+
|
|
153
|
+
# Trigger the full 3-OS × 2-harness matrix. `sha` is a required workflow input
|
|
154
|
+
# so the dispatched run is unambiguously bound to this commit.
|
|
155
|
+
gh workflow run release-smoke.yml --ref main -f sha="$PUBLISH_SHA"
|
|
156
|
+
|
|
157
|
+
# Give GitHub a moment to register the dispatched run.
|
|
158
|
+
sleep 10
|
|
159
|
+
|
|
160
|
+
# Find the run we just dispatched by matching head_sha. This is robust against
|
|
161
|
+
# concurrent dispatches (e.g. a manual UI retry from another tab) — never use
|
|
162
|
+
# `--limit 1` alone, which would race against any unrelated in-flight run.
|
|
163
|
+
RUN_ID=$(gh run list --workflow=release-smoke.yml --branch main \
|
|
164
|
+
--json databaseId,headSha \
|
|
165
|
+
--jq ".[] | select(.headSha == \"$PUBLISH_SHA\") | .databaseId" \
|
|
166
|
+
| head -1)
|
|
167
|
+
|
|
168
|
+
if [ -z "$RUN_ID" ]; then
|
|
169
|
+
echo "ERROR: no release-smoke run found for SHA $PUBLISH_SHA. Wait a few seconds and retry, or check https://github.com/eric-cielo/moflo/actions/workflows/release-smoke.yml" >&2
|
|
170
|
+
exit 1
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# Block until the run completes. Exits non-zero on failure.
|
|
174
|
+
gh run watch "$RUN_ID" --exit-status
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`release-smoke.yml` runs full consumer + populated smoke on Ubuntu/macOS/Windows, plus file-sync smoke on all three filesystems. Per-PR CI is intentionally Ubuntu-only to keep recurring cost manageable — the full cross-platform matrix is paid once here, at publish time, against the exact commit we're about to publish.
|
|
178
|
+
|
|
179
|
+
**Wall time expectations**: typical run is ~15-20 min once caches are warm. **The first dispatch after `release-smoke.yml` lands will have cold caches on every leg** (no prior runs of this workflow exist to populate the `consumer-warm` and `fastembed` caches per-OS). Expect ~25-35 min on that first dispatch — the macOS leg is the long pole. Subsequent dispatches reuse the cache and settle to the typical ~15 min.
|
|
180
|
+
|
|
181
|
+
**If `gh run watch` exits non-zero**: stop immediately. Inspect the failed leg with `gh run view "$RUN_ID" --log-failed`, fix the underlying issue, push the fix, then re-run this step (no need to rerun Steps 0–8 — the bump commit is already on main, and the SHA-filtered lookup will pick up the new dispatched run against the same SHA only after `cancel-in-progress: false` lets the previous run drain). Do not proceed to `npm publish` until release-smoke is green for the SHA you intend to publish.
|
|
182
|
+
|
|
147
183
|
### Step 9: Verify npm Authentication
|
|
148
184
|
|
|
149
185
|
```bash
|
|
@@ -223,8 +259,8 @@ Build: passed
|
|
|
223
259
|
Tests: skipped (CI-covered) | <N> files passed, <N> tests passed
|
|
224
260
|
Lint: skipped (CI-covered) | passed
|
|
225
261
|
Doctor: <N> passed, <N> warnings
|
|
226
|
-
Smoke (
|
|
227
|
-
|
|
262
|
+
Smoke (local): skipped (CI-covered) | passed
|
|
263
|
+
Release-smoke: green (run <id>, 3-OS × 2-harness)
|
|
228
264
|
Triggered gates: <list> | none
|
|
229
265
|
Published: moflo@<new-version>
|
|
230
266
|
Installed: moflo@<new-version> (devDependency)
|
|
@@ -257,5 +293,7 @@ For the common case — publishing right after a merged green PR — the default
|
|
|
257
293
|
- `docs/BUILD.md` — Step-by-step build/publish process this skill mirrors
|
|
258
294
|
- `.claude/guidance/internal/dogfooding.md` — Why we catch consumer-facing regressions first as our own dependency
|
|
259
295
|
- `harness/consumer-smoke/README.md` — Smoke harness profiles (clean + populated) that prove a consumer install works
|
|
260
|
-
- `.github/workflows/ci.yml` — Lint/build/test gates this skill skips by default
|
|
261
|
-
- `.github/workflows/consumer-install-smoke.yml` —
|
|
296
|
+
- `.github/workflows/ci.yml` — Lint/build/test gates this skill skips by default (ubuntu-only)
|
|
297
|
+
- `.github/workflows/consumer-install-smoke.yml` — Per-PR ubuntu-only smoke
|
|
298
|
+
- `.github/workflows/file-sync-smoke.yml` — Per-PR ubuntu-only file-sync smoke
|
|
299
|
+
- `.github/workflows/release-smoke.yml` — Full 3-OS × 2-harness matrix this skill dispatches at Step 8.5
|