moflo 4.12.3 → 4.12.4-rc.1
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-spell-engine.md +13 -10
- package/.claude/helpers/gate-hook.mjs +33 -2
- package/.claude/helpers/gate.cjs +63 -6
- package/.claude/skills/connector-builder/SKILL.md +5 -4
- package/.claude/skills/fl/phases.md +1 -1
- package/.claude/skills/fl/sdd.md +1 -1
- package/.claude/skills/spell-builder/SKILL.md +5 -4
- package/.claude/skills/spell-builder/architecture.md +4 -4
- package/.claude/skills/spell-builder/connectors/github-cli/README.md +22 -17
- package/.claude/skills/spell-builder/connectors/http/README.md +18 -11
- package/.claude/skills/spell-builder/connectors/local-outlook/README.md +8 -7
- package/.claude/skills/spell-builder/connectors/playwright/README.md +10 -8
- package/.claude/skills/spell-builder/steps/agent/README.md +25 -21
- package/.claude/skills/verify/SKILL.md +24 -3
- package/bin/gate-hook.mjs +33 -2
- package/bin/gate.cjs +63 -6
- package/bin/generate-code-map.mjs +70 -17
- package/bin/index-all.mjs +38 -0
- package/bin/index-guidance.mjs +8 -41
- package/bin/index-patterns.mjs +2 -2
- package/bin/index-tests.mjs +6 -4
- package/bin/lib/guidance-config.mjs +119 -0
- package/bin/lib/index-fingerprint.mjs +0 -0
- package/dist/src/cli/commands/analyze.js +1 -1
- package/dist/src/cli/commands/claims.js +2 -2
- package/dist/src/cli/commands/completions.js +1 -1
- package/dist/src/cli/commands/deployment.js +2 -2
- package/dist/src/cli/commands/diagnose.js +1 -1
- package/dist/src/cli/commands/doctor-checks-deep.js +1 -1
- package/dist/src/cli/commands/doctor.js +1 -1
- package/dist/src/cli/commands/embeddings.js +2 -2
- package/dist/src/cli/commands/github.js +1 -1
- package/dist/src/cli/commands/neural.js +2 -2
- package/dist/src/cli/commands/performance.js +2 -2
- package/dist/src/cli/commands/plugins.js +2 -2
- package/dist/src/cli/commands/providers.js +2 -2
- package/dist/src/cli/commands/retire.js +1 -1
- package/dist/src/cli/commands/route.js +1 -1
- package/dist/src/cli/commands/security.js +2 -2
- package/dist/src/cli/index.js +2 -2
- package/dist/src/cli/init/helpers-generator.js +38 -5
- package/dist/src/cli/init/moflo-yaml-template.js +11 -3
- package/dist/src/cli/init/settings-generator.js +10 -2
- package/dist/src/cli/mcp-server.js +5 -23
- package/dist/src/cli/mcp-tools/github-tools.js +18 -5
- package/dist/src/cli/mcp-tools/hooks-tools.js +25 -9
- package/dist/src/cli/mcp-tools/memory-tools.js +25 -4
- package/dist/src/cli/mcp-tools/neural-tools.js +26 -5
- package/dist/src/cli/mcp-tools/performance-tools.js +22 -4
- package/dist/src/cli/mcp-tools/security-tools.js +1 -1
- package/dist/src/cli/mcp-tools/synthetic.js +74 -0
- package/dist/src/cli/mcp-tools/system-tools.js +17 -2
- package/dist/src/cli/movector/flash-attention.js +1 -1
- package/dist/src/cli/movector/vector-db.js +1 -1
- package/dist/src/cli/services/daemon-lock.js +5 -2
- package/dist/src/cli/services/hook-block-hash.js +3 -1
- package/dist/src/cli/services/moflo-version.js +62 -0
- package/dist/src/cli/services/movector-training.js +1 -1
- package/dist/src/cli/services/spell-gate.js +1 -1
- package/dist/src/cli/spells/commands/agent-command.js +55 -25
- package/dist/src/cli/suggest.js +1 -1
- package/dist/src/cli/swarm/swarm-persistence.js +112 -0
- package/dist/src/cli/swarm/unified-coordinator.js +118 -3
- package/dist/src/cli/version.js +1 -1
- package/package.json +5 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Spell Engine — Definition Format & Step Types
|
|
2
2
|
|
|
3
|
-
**Purpose:** How to define a spell (YAML/JSON schema, arguments, steps, variable interpolation) and a reference for the
|
|
3
|
+
**Purpose:** How to define a spell (YAML/JSON schema, arguments, steps, variable interpolation) and a reference for the built-in step command types. For execution mechanics (running, dry-run, error codes, pause/resume, layering, credentials), see `.claude/guidance/moflo-spell-runner.md`.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -96,16 +96,16 @@ steps:
|
|
|
96
96
|
config:
|
|
97
97
|
command: "curl -s {args.api_url}"
|
|
98
98
|
- id: process
|
|
99
|
-
type:
|
|
99
|
+
type: bash
|
|
100
100
|
config:
|
|
101
|
-
|
|
101
|
+
command: 'claude -p "Analyze this response: {fetch-url.stdout}"'
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
---
|
|
105
105
|
|
|
106
106
|
## Step Command Types
|
|
107
107
|
|
|
108
|
-
**
|
|
108
|
+
**Fifteen built-in step types are registered automatically** (one of them, `agent`, is registered but not executable — see below). Each implements `execute()`, `validate()`, `describeOutputs()`, and optional `rollback()`. To add new step types via JS/TS files, YAML composite steps, or `moflo-step-*` npm packages, see `.claude/guidance/moflo-spell-custom-steps.md`.
|
|
109
109
|
|
|
110
110
|
### bash — Run a Shell Command
|
|
111
111
|
|
|
@@ -122,18 +122,21 @@ steps:
|
|
|
122
122
|
|
|
123
123
|
---
|
|
124
124
|
|
|
125
|
-
### agent —
|
|
125
|
+
### agent — NOT EXECUTABLE
|
|
126
|
+
|
|
127
|
+
**Do not use this step type.** It has never spawned a subagent — moflo has no agent spawner in the spell runner. It is still registered so existing spell YAML keeps parsing, but casting it now always fails with an explanatory error (#1334). Earlier versions returned `success: true` and a `result` string for work that never happened.
|
|
128
|
+
|
|
129
|
+
**To run a Claude subagent from a spell, use a `bash` step:**
|
|
126
130
|
|
|
127
131
|
```yaml
|
|
128
132
|
- id: research
|
|
129
|
-
type:
|
|
133
|
+
type: bash
|
|
130
134
|
config:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
background: false # Optional. Default false.
|
|
135
|
+
command: 'claude -p "Find all API endpoints in {args.directory}"'
|
|
136
|
+
timeout: 300000
|
|
134
137
|
```
|
|
135
138
|
|
|
136
|
-
**Outputs:**
|
|
139
|
+
**Outputs:** none — the step always fails. `agentType` and `prompt` are echoed in the failure for diagnosis only.
|
|
137
140
|
|
|
138
141
|
---
|
|
139
142
|
|
|
@@ -32,11 +32,42 @@ if (hookContext.tool_name) env.TOOL_NAME = hookContext.tool_name;
|
|
|
32
32
|
if (typeof hookContext.session_id === 'string' && hookContext.session_id) {
|
|
33
33
|
env.HOOK_SESSION_ID = hookContext.session_id;
|
|
34
34
|
}
|
|
35
|
+
// #1332: structured tool inputs are forwarded as JSON, not dropped.
|
|
36
|
+
//
|
|
37
|
+
// This previously forwarded ONLY string values, so any object-valued input was
|
|
38
|
+
// invisible to gate.cjs. That blocked the verify-before-done gate from reading
|
|
39
|
+
// `/verify`'s per-criterion verdict, which #1328 stores in memory_store's
|
|
40
|
+
// `metadata` — an object. Parsing the verdict out of the prose `value` string
|
|
41
|
+
// instead would re-create exactly the free-text dependency #1328 removed.
|
|
42
|
+
//
|
|
43
|
+
// Cross-platform (Rule #1): Windows caps a single environment variable at
|
|
44
|
+
// ~32KB and the whole block at ~32K wide chars, and exceeding it fails the
|
|
45
|
+
// spawn rather than truncating. Newly-forwarded values are therefore skipped
|
|
46
|
+
// when oversized, not clipped — a truncated JSON blob would parse as malformed
|
|
47
|
+
// on the far side and read as a corrupt record rather than an absent one.
|
|
48
|
+
// `metadata` is capped at 64KB by memory_store, so a real verdict never nears
|
|
49
|
+
// this. STRING values keep their previous uncapped behaviour byte-for-byte:
|
|
50
|
+
// gate.cjs reads TOOL_INPUT_command, and dropping an oversized heredoc command
|
|
51
|
+
// would silently stop check-dangerous-command from firing on the exact inputs
|
|
52
|
+
// most worth checking.
|
|
53
|
+
var MAX_STRUCTURED_LEN = 16384;
|
|
35
54
|
if (hookContext.tool_input && typeof hookContext.tool_input === 'object') {
|
|
36
55
|
Object.keys(hookContext.tool_input).forEach(function(key) {
|
|
37
|
-
|
|
38
|
-
|
|
56
|
+
var raw = hookContext.tool_input[key];
|
|
57
|
+
if (typeof raw === 'string') {
|
|
58
|
+
env['TOOL_INPUT_' + key] = raw;
|
|
59
|
+
return;
|
|
39
60
|
}
|
|
61
|
+
var val;
|
|
62
|
+
if (typeof raw === 'number' || typeof raw === 'boolean') {
|
|
63
|
+
val = String(raw);
|
|
64
|
+
} else if (raw && typeof raw === 'object') {
|
|
65
|
+
try { val = JSON.stringify(raw); } catch (e) { return; }
|
|
66
|
+
} else {
|
|
67
|
+
return; // null/undefined/function — nothing meaningful to forward
|
|
68
|
+
}
|
|
69
|
+
if (val.length > MAX_STRUCTURED_LEN) return;
|
|
70
|
+
env['TOOL_INPUT_' + key] = val;
|
|
40
71
|
});
|
|
41
72
|
}
|
|
42
73
|
|
package/.claude/helpers/gate.cjs
CHANGED
|
@@ -865,12 +865,47 @@ switch (command) {
|
|
|
865
865
|
// crediting them would let the gate pass without an end-to-end verify.
|
|
866
866
|
if (vName === 'verify') {
|
|
867
867
|
var s = readState();
|
|
868
|
-
|
|
868
|
+
// #1332: invoking /verify starts a verification; it does not conclude
|
|
869
|
+
// one. Clear any prior verdict so the run in progress cannot inherit the
|
|
870
|
+
// PASS from a previous issue and satisfy check-before-done on its own.
|
|
871
|
+
if (!s.verifyRun || s.verifyOutcome) {
|
|
872
|
+
s.verifyRun = true;
|
|
873
|
+
s.verifyOutcome = null;
|
|
874
|
+
writeState(s);
|
|
875
|
+
}
|
|
869
876
|
} else if (vName) {
|
|
870
877
|
process.stderr.write('gate: record-verify-run no-op — TOOL_INPUT_skill="' + vName + '" is not verify\n');
|
|
871
878
|
}
|
|
872
879
|
break;
|
|
873
880
|
}
|
|
881
|
+
case 'record-verify-outcome': {
|
|
882
|
+
// #1332. Fires PostToolUse on mcp__moflo__memory_store. `record-verify-run`
|
|
883
|
+
// above proves a verification was ATTEMPTED; this proves how it ENDED.
|
|
884
|
+
//
|
|
885
|
+
// The verdict is read from the structured record #1328 made /verify write
|
|
886
|
+
// to memory_store's `metadata` — never parsed out of the prose `value`,
|
|
887
|
+
// which is precisely the free-text dependency #1328 removed. gate-hook.mjs
|
|
888
|
+
// forwards the object as JSON (see its MAX_STRUCTURED_LEN note).
|
|
889
|
+
var mKey = process.env.TOOL_INPUT_key || '';
|
|
890
|
+
if (mKey.indexOf('verify:') !== 0) break;
|
|
891
|
+
var rawMeta = process.env.TOOL_INPUT_metadata || '';
|
|
892
|
+
if (!rawMeta) {
|
|
893
|
+
process.stderr.write('gate: record-verify-outcome — "' + mKey + '" carries no metadata; verdict not recorded\n');
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
var parsedMeta = null;
|
|
897
|
+
try { parsedMeta = JSON.parse(rawMeta); } catch (e) { parsedMeta = null; }
|
|
898
|
+
if (!parsedMeta || typeof parsedMeta !== 'object' || parsedMeta.type !== 'verify-record') break;
|
|
899
|
+
var overall = typeof parsedMeta.overall === 'string' ? parsedMeta.overall.toUpperCase() : '';
|
|
900
|
+
if (overall !== 'PASS' && overall !== 'FAIL' && overall !== 'UNVERIFIED') {
|
|
901
|
+
process.stderr.write('gate: record-verify-outcome — unrecognised overall="' + parsedMeta.overall + '"; treating as not-passing\n');
|
|
902
|
+
overall = 'UNVERIFIED';
|
|
903
|
+
}
|
|
904
|
+
var vs = readState();
|
|
905
|
+
vs.verifyOutcome = overall;
|
|
906
|
+
writeState(vs);
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
874
909
|
case 'reset-edit-gates': {
|
|
875
910
|
var fp = process.env.TOOL_INPUT_file_path || '';
|
|
876
911
|
// Inert files (markdown, lockfiles, CHANGELOG, .env.example) AND inert paths
|
|
@@ -883,12 +918,17 @@ switch (command) {
|
|
|
883
918
|
var resetTests = s.testsRun;
|
|
884
919
|
// A code edit invalidates a prior verification (Story #1274) — same as tests,
|
|
885
920
|
// including test-only edits (the criteria being verified may have moved).
|
|
886
|
-
|
|
921
|
+
// #1332: also fires when a verdict lingers without the flag, so no path
|
|
922
|
+
// can leave a recorded outcome behind after a source edit.
|
|
923
|
+
var resetVerify = s.verifyRun || !!s.verifyOutcome;
|
|
887
924
|
var resetSimplify = s.simplifyRun && !isTestOnly;
|
|
888
925
|
if (!resetTests && !resetSimplify && !resetVerify) break;
|
|
889
926
|
var gates = [];
|
|
890
927
|
if (resetTests) { s.testsRun = false; gates.push('tests'); }
|
|
891
|
-
|
|
928
|
+
// #1332: drop the recorded verdict with the flag. Leaving a stale PASS
|
|
929
|
+
// behind would let the next check-before-done pass on a verdict that
|
|
930
|
+
// describes pre-edit code.
|
|
931
|
+
if (resetVerify) { s.verifyRun = false; s.verifyOutcome = null; gates.push('verify'); }
|
|
892
932
|
if (resetSimplify) { s.simplifyRun = false; gates.push('simplify'); }
|
|
893
933
|
if (fp) {
|
|
894
934
|
s.lastResetBy = { file: fp, at: new Date().toISOString(), gates: gates };
|
|
@@ -1017,11 +1057,28 @@ switch (command) {
|
|
|
1017
1057
|
}
|
|
1018
1058
|
}
|
|
1019
1059
|
var sd = readState();
|
|
1020
|
-
|
|
1060
|
+
// #1332: gate on the OUTCOME, not on attendance. Before this, `verifyRun`
|
|
1061
|
+
// alone opened the gate, so a /verify returning FAIL satisfied it exactly
|
|
1062
|
+
// as a PASS did — a failing verdict is still a successful tool invocation.
|
|
1063
|
+
if (sd.verifyRun && sd.verifyOutcome === 'PASS') break;
|
|
1021
1064
|
process.stderr.write('BLOCKED: gh pr create requires verification before done:\n');
|
|
1022
|
-
|
|
1023
|
-
|
|
1065
|
+
// The four states need different remedies, so name which one applies
|
|
1066
|
+
// rather than emitting one message that fits none of them.
|
|
1067
|
+
var invalidated = sd.lastResetBy && sd.lastResetBy.file
|
|
1068
|
+
&& (sd.lastResetBy.gates || []).indexOf('verify') >= 0;
|
|
1069
|
+
if (!sd.verifyRun && invalidated) {
|
|
1070
|
+
process.stderr.write(' - a code edit invalidated the previous verification — re-run /verify\n');
|
|
1024
1071
|
process.stderr.write('Last gate reset: ' + sd.lastResetBy.file + ' (verify)\n');
|
|
1072
|
+
} else if (!sd.verifyRun) {
|
|
1073
|
+
process.stderr.write(' - the change has not been verified since the last code edit (run /verify)\n');
|
|
1074
|
+
} else if (sd.verifyOutcome === 'FAIL' || sd.verifyOutcome === 'UNVERIFIED') {
|
|
1075
|
+
process.stderr.write(' - /verify ran and returned ' + sd.verifyOutcome + ' — fix the failing criteria, then re-run /verify\n');
|
|
1076
|
+
process.stderr.write(' (a FAIL is a real result, not a gate error; the PR is blocked because the change did not meet its acceptance criteria)\n');
|
|
1077
|
+
} else {
|
|
1078
|
+
// Ran, but no verdict reached the gate: /verify was invoked and never
|
|
1079
|
+
// recorded a structured outcome (interrupted, or it stored prose only).
|
|
1080
|
+
process.stderr.write(' - /verify ran but recorded no verdict — re-run it so it stores a structured result\n');
|
|
1081
|
+
process.stderr.write(' (Step 5 of the verify skill must pass metadata.overall to memory_store)\n');
|
|
1025
1082
|
}
|
|
1026
1083
|
process.stderr.write('Disable via moflo.yaml:\n');
|
|
1027
1084
|
process.stderr.write(' gates:\n verify_before_done: false\n');
|
|
@@ -118,13 +118,14 @@ steps:
|
|
|
118
118
|
command: "echo 'preparing...'"
|
|
119
119
|
|
|
120
120
|
- name: use-<name>
|
|
121
|
-
type:
|
|
121
|
+
type: <name> # the step command registered in Step 4
|
|
122
122
|
config:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
Access via context.tools.execute('<name>', '<action-1>', { ... })
|
|
123
|
+
action: <action-1>
|
|
124
|
+
# ...action params
|
|
126
125
|
```
|
|
127
126
|
|
|
127
|
+
**Do not reach a connector from an `agent` step.** That step type has never been executable. A connector is reached from its own step command (Step 4 above), from a composite step's `tool` action, or from a custom step command.
|
|
128
|
+
|
|
128
129
|
---
|
|
129
130
|
|
|
130
131
|
## Building a Step Command
|
|
@@ -182,7 +182,7 @@ git commit -m "type(scope): description
|
|
|
182
182
|
|
|
183
183
|
Closes #<issue-number>
|
|
184
184
|
|
|
185
|
-
Co-Authored-By: moflo <noreply@
|
|
185
|
+
Co-Authored-By: moflo <noreply@cielolimitada.com>"
|
|
186
186
|
```
|
|
187
187
|
|
|
188
188
|
### 5.1b Verify-before-done (default; skipped only with `--no-verify`)
|
package/.claude/skills/fl/sdd.md
CHANGED
|
@@ -52,7 +52,7 @@ Artifacts live at `<specs_dir>/<slug>/{spec,plan}.md` — default `.moflo/specs`
|
|
|
52
52
|
|
|
53
53
|
Runs at step 8 of the full-mode flow, before the PR — **by default** and always under `--sdd`; `--no-verify` skips it for one run.
|
|
54
54
|
|
|
55
|
-
**Delegate to the `/verify` skill** — `Skill({ skill: "verify" })`, passing the issue number or spec slug. It owns the mechanics (single source of truth — don't restate them here): locate the acceptance criteria (plan, else ticket) → reuse the Tests-phase run (no double verify) → map each criterion to evidence → run only uncovered checks → record its own outcome to memory (`learnings`, `verify:<slug-or-issue>`) → return a per-criterion PASS/FAIL.
|
|
55
|
+
**Delegate to the `/verify` skill** — `Skill({ skill: "verify" })`, passing the issue number or spec slug. It owns the mechanics (single source of truth — don't restate them here): locate the acceptance criteria (plan, else ticket) → reuse the Tests-phase run (no double verify) → map each criterion to evidence → run only uncovered checks → record its own outcome to memory (`learnings`, `verify:<slug-or-issue>`) → return a per-criterion PASS/FAIL. **The verdict is the point, not the invocation** (#1332). Invoking `/verify` trips `record-verify-run`, but that alone no longer opens `check-before-done` — the gate requires the recorded verdict to be `PASS`. A run returning FAIL leaves `gh pr create` blocked, which is the intended behaviour: the change did not meet its acceptance criteria. The verdict reaches the gate from the structured record `/verify` Step 5 writes to `memory_store` under `metadata.overall` (#1328), so a run that stores prose only is treated as *no verdict* and also blocks. Describing verification in prose has never satisfied the gate. A source edit after verifying invalidates both the flag and the verdict — re-run `/verify`. Full how-to: `.claude/skills/verify/SKILL.md`.
|
|
56
56
|
|
|
57
57
|
`/ward` and `/quicken` stay targeted audits, not the completion gate.
|
|
58
58
|
|
|
@@ -314,11 +314,12 @@ steps:
|
|
|
314
314
|
output: audit-result
|
|
315
315
|
|
|
316
316
|
- id: analyze-findings
|
|
317
|
-
type:
|
|
317
|
+
type: bash
|
|
318
318
|
config:
|
|
319
|
-
|
|
320
|
-
Analyze
|
|
321
|
-
Audit output: {scan-deps.
|
|
319
|
+
command: |
|
|
320
|
+
claude -p "Analyze these npm audit results and filter for severity >= {args.severity}.
|
|
321
|
+
Audit output: {scan-deps.stdout}"
|
|
322
|
+
timeout: 300000
|
|
322
323
|
output: analysis
|
|
323
324
|
|
|
324
325
|
- id: save-report
|
|
@@ -136,11 +136,11 @@ steps:
|
|
|
136
136
|
output: inbox # ← stores output as "inbox"
|
|
137
137
|
|
|
138
138
|
- id: process
|
|
139
|
-
type:
|
|
139
|
+
type: bash
|
|
140
140
|
config:
|
|
141
|
-
|
|
142
|
-
Emails: {inbox.emails}
|
|
143
|
-
Total: {inbox.totalEmails}
|
|
141
|
+
command: |
|
|
142
|
+
claude -p "Emails: {inbox.emails} # ← references inbox output
|
|
143
|
+
Total: {inbox.totalEmails}"
|
|
144
144
|
```
|
|
145
145
|
|
|
146
146
|
**Variable reference rules:**
|
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
# `github-cli` — GitHub CLI Connector
|
|
2
2
|
|
|
3
|
-
**Purpose:** Use this connector
|
|
3
|
+
**Purpose:** Use this connector as the backing connector for the `github` step command, or from a custom step command. Choose this when you need programmatic access to GitHub issues, PRs, and repos.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
|
+
From a spell, reach this connector through the `github` step, which delegates to it:
|
|
8
|
+
|
|
7
9
|
```yaml
|
|
8
|
-
- id:
|
|
9
|
-
type:
|
|
10
|
+
- id: fetch-bug
|
|
11
|
+
type: github
|
|
10
12
|
config:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
repo: 'my-org/backend-api',
|
|
15
|
-
labels: ['bug'],
|
|
16
|
-
state: 'open'
|
|
17
|
-
})
|
|
13
|
+
action: issue-fetch
|
|
14
|
+
issue: 42
|
|
15
|
+
fields: ["number", "title", "labels", "state"]
|
|
18
16
|
```
|
|
19
17
|
|
|
18
|
+
> Earlier revisions of this file showed an `agent` step calling `context.tools.execute(...)` from a prompt. The `agent` step type has never been executable — use the `github` step, a composite step's `tool` action, or a custom step command.
|
|
19
|
+
|
|
20
20
|
## Actions
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
|
25
|
-
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
22
|
+
Taken from `VALID_ACTIONS` in `src/cli/spells/connectors/github-cli.ts`. `issue-create`, `issue-list`, `pr-list` and `repo-view` were listed here previously but have never existed.
|
|
23
|
+
|
|
24
|
+
| Action | Required params | Description |
|
|
25
|
+
|--------|-----------------|-------------|
|
|
26
|
+
| `issue-fetch` | `issue` | Fetch issue details as JSON (`fields` selects columns) |
|
|
27
|
+
| `issue-edit` | `issue` | Edit an existing issue |
|
|
28
|
+
| `pr-create` | `title` | Create a pull request |
|
|
29
|
+
| `pr-merge` | `pr` or `issue` | Merge a PR (`mergeMethod`: squash \| merge \| rebase) |
|
|
30
|
+
| `pr-find` | `head` or `search` | Find a PR by head branch or search query |
|
|
31
|
+
| `label` | (`issue` or `pr`) + `labels` | Add/remove labels |
|
|
32
|
+
| `comment` | (`issue` or `pr`) + `body` | Post a comment |
|
|
33
|
+
| `repo-info` | — | View repository metadata |
|
|
29
34
|
|
|
30
35
|
## Direct Usage
|
|
31
36
|
|
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
# `http` — HTTP Requests
|
|
2
2
|
|
|
3
|
-
**Purpose:** Use this connector to make HTTP requests to any URL
|
|
3
|
+
**Purpose:** Use this connector to make HTTP requests to any URL. Choose this when you need to call REST APIs, fetch data, or post payloads during spell execution.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
|
+
Unlike `playwright`/`github-cli`/`local-outlook`, this connector has **no dedicated step type**. Reach it from a composite (YAML) step's `tool` action:
|
|
8
|
+
|
|
7
9
|
```yaml
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
name: fetch-status
|
|
11
|
+
inputs:
|
|
12
|
+
token: { type: string }
|
|
13
|
+
actions:
|
|
14
|
+
- tool: http
|
|
15
|
+
action: request
|
|
16
|
+
params:
|
|
17
|
+
method: GET
|
|
18
|
+
url: "https://api.myapp.com/deploy/status"
|
|
19
|
+
headers:
|
|
20
|
+
Authorization: "Bearer ${inputs.token}"
|
|
18
21
|
```
|
|
19
22
|
|
|
23
|
+
…or from a custom step command via the Direct Usage API below. See `.claude/guidance/moflo-spell-custom-steps.md`.
|
|
24
|
+
|
|
25
|
+
> Earlier revisions of this file showed an `agent` step calling `context.tools.execute(...)` from a prompt. The `agent` step type has never been executable.
|
|
26
|
+
|
|
20
27
|
## Actions
|
|
21
28
|
|
|
22
29
|
| Action | Parameters | Description |
|
|
@@ -4,18 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
|
+
From a spell, reach this connector through the `outlook` step, which delegates to it:
|
|
8
|
+
|
|
7
9
|
```yaml
|
|
8
10
|
- id: check-for-invoices
|
|
9
|
-
type:
|
|
11
|
+
type: outlook
|
|
10
12
|
config:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
query: 'invoice from:billing@vendor.com',
|
|
15
|
-
limit: 5
|
|
16
|
-
})
|
|
13
|
+
action: search
|
|
14
|
+
query: "invoice from:billing@vendor.com"
|
|
15
|
+
limit: 5
|
|
17
16
|
```
|
|
18
17
|
|
|
18
|
+
> Earlier revisions of this file showed an `agent` step calling `context.tools.execute(...)` from a prompt. The `agent` step type has never been executable — use the `outlook` step, a composite step's `tool` action, or a custom step command.
|
|
19
|
+
|
|
19
20
|
## Actions
|
|
20
21
|
|
|
21
22
|
| Action | Parameters | Description |
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
# `playwright` — Browser Automation Connector
|
|
2
2
|
|
|
3
|
-
**Purpose:** Use this connector for low-level browser automation via Playwright. Choose this when you need direct browser control from
|
|
3
|
+
**Purpose:** Use this connector for low-level browser automation via Playwright. Choose this when you need direct browser control from a step command, or when building browser-based connectors like `local-outlook`.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
|
+
From a spell, reach this connector through the `browser` step, which delegates to it:
|
|
8
|
+
|
|
7
9
|
```yaml
|
|
8
10
|
- id: take-screenshot
|
|
9
|
-
type:
|
|
11
|
+
type: browser
|
|
10
12
|
config:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
})
|
|
16
|
-
Then call context.tools.execute('playwright', 'screenshot', {})
|
|
13
|
+
actions:
|
|
14
|
+
- action: navigate
|
|
15
|
+
url: "https://myapp.com"
|
|
16
|
+
- action: screenshot
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
> Earlier revisions of this file showed an `agent` step calling `context.tools.execute(...)` from a prompt. The `agent` step type has never been executable — use the `browser` step, a composite step's `tool` action, or a custom step command.
|
|
20
|
+
|
|
19
21
|
## Actions
|
|
20
22
|
|
|
21
23
|
| Action | Parameters | Description |
|
|
@@ -1,36 +1,40 @@
|
|
|
1
|
-
# `agent` —
|
|
1
|
+
# `agent` — NOT EXECUTABLE
|
|
2
2
|
|
|
3
|
-
**Purpose:**
|
|
3
|
+
**Purpose:** Document that this step type does not work, so it is not offered when authoring a spell. It is registered but has never spawned a subagent — moflo has no agent spawner in the spell runner. Casting it always fails.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Do not add `agent` steps to a spell.** Previously the step returned `success: true` with `result: "Agent task prepared: <type>"`, so spells containing one completed green and downstream steps consumed that string as though it were agent output. It now fails with an explanatory error instead.
|
|
6
|
+
|
|
7
|
+
The type stays registered on purpose: removing it would turn a silently-useless step into a hard parse error for any existing spell YAML that contains one.
|
|
8
|
+
|
|
9
|
+
## Use this instead
|
|
10
|
+
|
|
11
|
+
Run the Claude CLI from a `bash` step:
|
|
6
12
|
|
|
7
13
|
```yaml
|
|
8
14
|
- id: research
|
|
9
|
-
type:
|
|
15
|
+
type: bash
|
|
10
16
|
config:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
model: claude-sonnet-5
|
|
14
|
-
background: false
|
|
17
|
+
command: 'claude -p "Find all REST API endpoints in src/ and list their HTTP methods and paths"'
|
|
18
|
+
timeout: 300000
|
|
15
19
|
```
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
This is what moflo's own shipped spells do — see `src/cli/spells/definitions/epic-auto-merge.yaml`.
|
|
22
|
+
|
|
23
|
+
**Note on cost:** a `bash` step invoking `claude -p` has no spend ceiling, which matters most for daemon-scheduled spells. Set a `timeout` and prefer `failOnError: true`.
|
|
24
|
+
|
|
25
|
+
## If you are reading an older spell
|
|
26
|
+
|
|
27
|
+
| Old `agent` config | Replacement |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `prompt` | the prompt text inside `claude -p "..."` |
|
|
30
|
+
| `agentType` | no equivalent — describe the role in the prompt |
|
|
31
|
+
| `background` | no equivalent — the bash step waits |
|
|
18
32
|
|
|
19
|
-
|
|
20
|
-
|-------|----------|---------|-------------|
|
|
21
|
-
| `prompt` | Yes | — | Task prompt for the agent |
|
|
22
|
-
| `agentType` | No | `coder` | Agent specialization: `researcher`, `coder`, `tester`, `reviewer` |
|
|
23
|
-
| `model` | No | system default | Model override (e.g. `claude-sonnet-5`) |
|
|
24
|
-
| `systemPrompt` | No | — | Custom system prompt replacing the default |
|
|
25
|
-
| `background` | No | `false` | Run without waiting for the result |
|
|
33
|
+
`model` and `systemPrompt` were documented here previously but were never implemented — the step's `configSchema` only ever accepted `prompt`, `agentType`, and `background`.
|
|
26
34
|
|
|
27
35
|
## Outputs
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
|-------|------|-------------|
|
|
31
|
-
| `result` | string | Agent response text |
|
|
32
|
-
| `agentType` | string | Agent type that was used |
|
|
33
|
-
| `prompt` | string | Prompt that was sent |
|
|
37
|
+
None. The step always fails. `agentType` and `prompt` are echoed in the failure data for diagnosis only; there is no `result`.
|
|
34
38
|
|
|
35
39
|
## Source
|
|
36
40
|
|
|
@@ -14,7 +14,9 @@ Prove the current change **actually does what it was supposed to** before it shi
|
|
|
14
14
|
|
|
15
15
|
## What satisfies the gate
|
|
16
16
|
|
|
17
|
-
Invoking this skill (name `verify`) trips the `record-verify-run` hook, which flips the `verifyRun` state the `check-before-done`
|
|
17
|
+
Invoking this skill (name `verify`) trips the `record-verify-run` hook, which flips the `verifyRun` state. **That alone does not open the gate** (#1332): `check-before-done` also requires the recorded verdict to be `PASS`, which reaches it from the `metadata.overall` your Step 5 store writes. So a run returning FAIL leaves `gh pr create` blocked — as it should, since the change did not meet its criteria — and a run that stores prose without `metadata` counts as *no verdict* and blocks too.
|
|
18
|
+
|
|
19
|
+
**Only `/verify` satisfies it** — `/ward` and `/quicken` are targeted audits, not an end-to-end verification. A source edit *after* verifying invalidates both the flag and the verdict, so run `/verify` as the last step before the PR.
|
|
18
20
|
|
|
19
21
|
## Step 0 — Memory first
|
|
20
22
|
|
|
@@ -71,17 +73,36 @@ Overall verdict is PASS iff no row is FAIL or UNVERIFIED.
|
|
|
71
73
|
|
|
72
74
|
## Step 5 — Record the outcome to memory
|
|
73
75
|
|
|
74
|
-
Always store the result (feeds routing/learning and the SDD trail)
|
|
76
|
+
Always store the result (feeds routing/learning and the SDD trail). Store it **twice over**: the prose summary in `value`, and the Step 4 table itself — unflattened — in `metadata`.
|
|
75
77
|
|
|
76
78
|
```
|
|
77
79
|
mcp__moflo__memory_store {
|
|
78
80
|
namespace: "learnings",
|
|
79
81
|
key: "verify:<slug-or-issue>",
|
|
80
82
|
value: "<overall PASS/FAIL> — per-criterion: <criterion → evidence → verdict>; commit <sha>",
|
|
81
|
-
tags: ["verify", "sdd"]
|
|
83
|
+
tags: ["verify", "sdd"],
|
|
84
|
+
metadata: {
|
|
85
|
+
type: "verify-record",
|
|
86
|
+
issue: "<slug-or-issue>",
|
|
87
|
+
commit: "<sha>",
|
|
88
|
+
overall: "PASS" | "FAIL" | "UNVERIFIED",
|
|
89
|
+
verifiedAt: "<ISO-8601>",
|
|
90
|
+
criteria: [
|
|
91
|
+
{ id: 1, statement: "<criterion>", verdict: "PASS" | "FAIL" | "UNVERIFIED",
|
|
92
|
+
evidence: "<test name / observed output>", freshlyExecuted: true }
|
|
93
|
+
]
|
|
94
|
+
}
|
|
82
95
|
}
|
|
83
96
|
```
|
|
84
97
|
|
|
98
|
+
**Why both.** `value` is what gets embedded for semantic search, so it stays prose — a JSON blob there would degrade every future `learnings` search. `metadata` is stored verbatim and not embedded, so the structure survives without that cost. `memory_retrieve` returns the parsed `metadata` object for non-chunk entries, which is what makes the record readable rather than write-only.
|
|
99
|
+
|
|
100
|
+
**`freshlyExecuted` is required on every criterion, not optional.** Step 3 deliberately permits *citing* an earlier green run instead of re-executing. That is a sound cost optimisation, but it means evidence may be a citation rather than a fresh result — set `freshlyExecuted: false` when you cited. Without that flag a later reader silently inherits stale evidence and cannot tell a re-verified criterion from a re-cited one.
|
|
101
|
+
|
|
102
|
+
**Never record an exit code.** Claude Code's `tool_response` for Bash carries `stdout`/`stderr` but **no exit status**, and PostToolUse does not fire at all when a command exits non-zero (#1322). `evidence` is therefore descriptive by necessity; a field named `exitCode` would be agent-narrated fiction, which is the problem this record exists to remove.
|
|
103
|
+
|
|
104
|
+
`overall` MUST agree with the Step 4 rule — PASS iff no criterion is FAIL or UNVERIFIED. Record a FAIL as a FAIL; the store is the audit trail, and a verdict that only ever reads PASS is worth nothing.
|
|
105
|
+
|
|
85
106
|
## Step 6 — Report
|
|
86
107
|
|
|
87
108
|
One concise summary: overall verdict, the per-criterion table, and — on FAIL/UNVERIFIED — exactly which criteria are unproven and what evidence is missing. Do **not** open the PR or edit code from here; hand the verdict back to the caller.
|
package/bin/gate-hook.mjs
CHANGED
|
@@ -32,11 +32,42 @@ if (hookContext.tool_name) env.TOOL_NAME = hookContext.tool_name;
|
|
|
32
32
|
if (typeof hookContext.session_id === 'string' && hookContext.session_id) {
|
|
33
33
|
env.HOOK_SESSION_ID = hookContext.session_id;
|
|
34
34
|
}
|
|
35
|
+
// #1332: structured tool inputs are forwarded as JSON, not dropped.
|
|
36
|
+
//
|
|
37
|
+
// This previously forwarded ONLY string values, so any object-valued input was
|
|
38
|
+
// invisible to gate.cjs. That blocked the verify-before-done gate from reading
|
|
39
|
+
// `/verify`'s per-criterion verdict, which #1328 stores in memory_store's
|
|
40
|
+
// `metadata` — an object. Parsing the verdict out of the prose `value` string
|
|
41
|
+
// instead would re-create exactly the free-text dependency #1328 removed.
|
|
42
|
+
//
|
|
43
|
+
// Cross-platform (Rule #1): Windows caps a single environment variable at
|
|
44
|
+
// ~32KB and the whole block at ~32K wide chars, and exceeding it fails the
|
|
45
|
+
// spawn rather than truncating. Newly-forwarded values are therefore skipped
|
|
46
|
+
// when oversized, not clipped — a truncated JSON blob would parse as malformed
|
|
47
|
+
// on the far side and read as a corrupt record rather than an absent one.
|
|
48
|
+
// `metadata` is capped at 64KB by memory_store, so a real verdict never nears
|
|
49
|
+
// this. STRING values keep their previous uncapped behaviour byte-for-byte:
|
|
50
|
+
// gate.cjs reads TOOL_INPUT_command, and dropping an oversized heredoc command
|
|
51
|
+
// would silently stop check-dangerous-command from firing on the exact inputs
|
|
52
|
+
// most worth checking.
|
|
53
|
+
var MAX_STRUCTURED_LEN = 16384;
|
|
35
54
|
if (hookContext.tool_input && typeof hookContext.tool_input === 'object') {
|
|
36
55
|
Object.keys(hookContext.tool_input).forEach(function(key) {
|
|
37
|
-
|
|
38
|
-
|
|
56
|
+
var raw = hookContext.tool_input[key];
|
|
57
|
+
if (typeof raw === 'string') {
|
|
58
|
+
env['TOOL_INPUT_' + key] = raw;
|
|
59
|
+
return;
|
|
39
60
|
}
|
|
61
|
+
var val;
|
|
62
|
+
if (typeof raw === 'number' || typeof raw === 'boolean') {
|
|
63
|
+
val = String(raw);
|
|
64
|
+
} else if (raw && typeof raw === 'object') {
|
|
65
|
+
try { val = JSON.stringify(raw); } catch (e) { return; }
|
|
66
|
+
} else {
|
|
67
|
+
return; // null/undefined/function — nothing meaningful to forward
|
|
68
|
+
}
|
|
69
|
+
if (val.length > MAX_STRUCTURED_LEN) return;
|
|
70
|
+
env['TOOL_INPUT_' + key] = val;
|
|
40
71
|
});
|
|
41
72
|
}
|
|
42
73
|
|