moflo 4.12.3 → 4.12.4-rc.2
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/agents/core/coder.md +2 -3
- package/.claude/agents/core/researcher.md +3 -4
- package/.claude/agents/core/reviewer.md +6 -8
- package/.claude/guidance/shipped/moflo-spell-engine.md +13 -10
- package/.claude/helpers/gate-hook.mjs +67 -2
- package/.claude/helpers/gate.cjs +173 -12
- package/.claude/helpers/hook-handler.cjs +0 -0
- package/.claude/helpers/prompt-hook.mjs +0 -0
- 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 +67 -2
- package/bin/gate.cjs +173 -12
- 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/agent.js +1 -89
- package/dist/src/cli/commands/analyze.js +1 -1
- package/dist/src/cli/commands/benchmark.js +11 -8
- package/dist/src/cli/commands/claims.js +2 -2
- package/dist/src/cli/commands/completions.js +1 -1
- package/dist/src/cli/commands/config.js +256 -96
- package/dist/src/cli/commands/deployment.js +6 -6
- package/dist/src/cli/commands/diagnose.js +1 -1
- package/dist/src/cli/commands/doctor-checks-config.js +18 -22
- package/dist/src/cli/commands/doctor-checks-deep.js +1 -1
- package/dist/src/cli/commands/doctor-fixes.js +36 -11
- package/dist/src/cli/commands/doctor.js +1 -1
- package/dist/src/cli/commands/embeddings.js +64 -23
- package/dist/src/cli/commands/github.js +1 -1
- package/dist/src/cli/commands/guidance.js +10 -10
- package/dist/src/cli/commands/hive-mind.js +8 -70
- package/dist/src/cli/commands/hooks.js +32 -219
- package/dist/src/cli/commands/mcp.js +5 -2
- package/dist/src/cli/commands/memory.js +59 -46
- package/dist/src/cli/commands/neural.js +21 -21
- package/dist/src/cli/commands/performance.js +14 -7
- 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 +4 -4
- package/dist/src/cli/commands/status.js +18 -13
- package/dist/src/cli/commands/task.js +8 -0
- package/dist/src/cli/config/cli-config-store.js +326 -0
- package/dist/src/cli/hooks/workers/index.js +15 -9
- package/dist/src/cli/index.js +43 -33
- package/dist/src/cli/init/claudemd-generator.js +3 -3
- package/dist/src/cli/init/helpers-generator.js +203 -20
- 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-client.js +12 -2
- package/dist/src/cli/mcp-server.js +5 -23
- package/dist/src/cli/mcp-tools/analysis-tools.js +103 -0
- package/dist/src/cli/mcp-tools/coverage-tools.js +206 -0
- package/dist/src/cli/mcp-tools/hive-mind-tools.js +71 -0
- package/dist/src/cli/mcp-tools/hooks-tools.js +25 -9
- package/dist/src/cli/mcp-tools/memory-admin-tools.js +538 -0
- package/dist/src/cli/mcp-tools/memory-tools.js +26 -5
- package/dist/src/cli/mcp-tools/neural-tools.js +56 -87
- package/dist/src/cli/mcp-tools/performance-tools.js +76 -39
- package/dist/src/cli/mcp-tools/progress-tools.js +142 -0
- package/dist/src/cli/mcp-tools/security-tools.js +1 -1
- package/dist/src/cli/mcp-tools/session-tools.js +116 -0
- package/dist/src/cli/mcp-tools/synthetic.js +74 -0
- package/dist/src/cli/mcp-tools/system-tools.js +126 -78
- package/dist/src/cli/mcp-tools/task-tools.js +101 -1
- package/dist/src/cli/movector/coverage-router.js +48 -6
- package/dist/src/cli/movector/diff-classifier.js +8 -6
- package/dist/src/cli/movector/flash-attention.js +1 -1
- package/dist/src/cli/movector/vector-db.js +1 -1
- package/dist/src/cli/parser.js +81 -12
- 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 +6 -4
- package/dist/src/cli/services/worker-daemon.js +37 -7
- package/dist/src/cli/shared/utils/load-average.js +59 -0
- 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
- package/dist/src/cli/mcp-tools/github-tools.js +0 -318
|
@@ -236,10 +236,9 @@ mcp__moflo__performance_benchmark {
|
|
|
236
236
|
iterations: 10
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
//
|
|
239
|
+
// Read this process's CPU / memory / heap usage
|
|
240
240
|
mcp__moflo__performance_report {
|
|
241
|
-
|
|
242
|
-
metrics: ["response-time", "memory-usage"]
|
|
241
|
+
format: "detailed"
|
|
243
242
|
}
|
|
244
243
|
```
|
|
245
244
|
|
|
@@ -155,10 +155,9 @@ mcp__moflo__memory_search {
|
|
|
155
155
|
|
|
156
156
|
### Analysis Tools
|
|
157
157
|
```javascript
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
analysis_type: "code_quality"
|
|
158
|
+
// Understand what a change touches and how risky it is
|
|
159
|
+
mcp__moflo__analyze_diff {
|
|
160
|
+
ref: "main"
|
|
162
161
|
}
|
|
163
162
|
|
|
164
163
|
// Track research metrics
|
|
@@ -304,16 +304,14 @@ mcp__moflo__memory_retrieve {
|
|
|
304
304
|
|
|
305
305
|
### Code Analysis
|
|
306
306
|
```javascript
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
analysis_type: "code_quality"
|
|
307
|
+
// Assess the risk and shape of the change under review
|
|
308
|
+
mcp__moflo__analyze_diff {
|
|
309
|
+
ref: "main"
|
|
311
310
|
}
|
|
312
311
|
|
|
313
|
-
//
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
analysis_type: "security"
|
|
312
|
+
// Scan the diff for prompt-injection and unsafe content
|
|
313
|
+
mcp__moflo__aidefence_scan {
|
|
314
|
+
content: "<the diff or file under review>"
|
|
317
315
|
}
|
|
318
316
|
```
|
|
319
317
|
|
|
@@ -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,14 +32,79 @@ 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
|
|
|
74
|
+
// #1322: forward the parts of tool_response that actually exist, so a gate can
|
|
75
|
+
// observe an OUTCOME rather than only the intent it was handed.
|
|
76
|
+
//
|
|
77
|
+
// Claude Code's PostToolUse payload carries NO exit status — probed on v2.1.220,
|
|
78
|
+
// tool_response for a Bash call is {stdout, stderr, interrupted, isImage,
|
|
79
|
+
// noOutputExpected}. PostToolUse also does not fire at all when the command
|
|
80
|
+
// exits non-zero, so the only case a gate can still be fooled by is an exit code
|
|
81
|
+
// MASKED by a pipe or `|| true`, where the response looks clean. The runner's
|
|
82
|
+
// own output is the sole remaining signal; record-test-run reads it in gate.cjs.
|
|
83
|
+
//
|
|
84
|
+
// Tail, not head. Every test runner prints its pass/fail summary LAST, so
|
|
85
|
+
// clipping the front of a long log would discard the exact lines this exists to
|
|
86
|
+
// read. Bounds are deliberately tight — Windows caps the whole environment
|
|
87
|
+
// block at ~32K wide chars and fails the spawn rather than truncating, and
|
|
88
|
+
// TOOL_INPUT_command is already forwarded uncapped alongside these.
|
|
89
|
+
var MAX_RESPONSE_STDOUT = 4096;
|
|
90
|
+
var MAX_RESPONSE_STDERR = 2048;
|
|
91
|
+
function tailOf(value, max) {
|
|
92
|
+
return value.length > max ? value.slice(value.length - max) : value;
|
|
93
|
+
}
|
|
94
|
+
if (hookContext.tool_response && typeof hookContext.tool_response === 'object') {
|
|
95
|
+
var resp = hookContext.tool_response;
|
|
96
|
+
if (typeof resp.stdout === 'string' && resp.stdout) {
|
|
97
|
+
env.TOOL_RESPONSE_stdout = tailOf(resp.stdout, MAX_RESPONSE_STDOUT);
|
|
98
|
+
}
|
|
99
|
+
if (typeof resp.stderr === 'string' && resp.stderr) {
|
|
100
|
+
env.TOOL_RESPONSE_stderr = tailOf(resp.stderr, MAX_RESPONSE_STDERR);
|
|
101
|
+
}
|
|
102
|
+
// Boolean — the string-typed forwarding above would drop it silently.
|
|
103
|
+
if (typeof resp.interrupted === 'boolean') {
|
|
104
|
+
env.TOOL_RESPONSE_interrupted = String(resp.interrupted);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
43
108
|
// Run gate.cjs with the enriched environment
|
|
44
109
|
var projectDir = (env.CLAUDE_PROJECT_DIR || process.cwd()).replace(/^\/([a-z])\//i, '$1:/');
|
|
45
110
|
var gateScript = resolve(projectDir, '.claude/helpers/gate.cjs');
|
package/.claude/helpers/gate.cjs
CHANGED
|
@@ -139,6 +139,14 @@ var EXEMPT = ['.claude/', '.claude\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state
|
|
|
139
139
|
// failures and "fixes" them with allow rules — which cannot override a hook
|
|
140
140
|
// block at all (#1307 finding 5). Naming the gate makes the cause self-evident.
|
|
141
141
|
var GATE_ORIGIN_NOTE = 'This is a moflo hook, not a Claude Code permission rule — allow-rules cannot override it.';
|
|
142
|
+
|
|
143
|
+
// #1348 — the pre-PR gates are order-dependent and each block message named only
|
|
144
|
+
// its own missing gate, so a caller could satisfy them one at a time forever:
|
|
145
|
+
// /flo-simplify may edit code (which resets tests and verify), and /verify must
|
|
146
|
+
// not, so simplify strictly precedes verify. Both blocking gates print this.
|
|
147
|
+
// /verify's Step 5 memory_store carries the verdict AND stamps learnings, which
|
|
148
|
+
// is why learnings has no separate step here.
|
|
149
|
+
var ORDER_HINT = 'Order that satisfies all of them: tests green -> /flo-simplify (re-run tests if it edits) -> /verify -> its memory_store verdict -> gh pr create\n';
|
|
142
150
|
var GATE_DISABLE_NOTE = 'Disable per-gate via moflo.yaml: gates: memory_first: false';
|
|
143
151
|
|
|
144
152
|
// #1294 Finding 3 — reads/scans of EPHEMERAL files under the OS temp dir
|
|
@@ -498,6 +506,53 @@ function applyPromptStateReset(state, promptText) {
|
|
|
498
506
|
// and language-native test commands. The bare-runner arm is anchored so that
|
|
499
507
|
// `npm install jest`, `grep -r vitest src/`, and similar don't false-positive.
|
|
500
508
|
var TEST_RUNNER_RE = /(?:^|[^a-z])(?:npm|yarn|pnpm|bun)\s+(?:run\s+)?(?:test|t)(?:[:\s]|$)|\b(?:npx|pnpx)\s+(?:vitest|jest|mocha|ava|tap|jasmine|pytest)\b|(?:^|;|&&|\|\|)\s*(?:vitest|jest|pytest|mocha|jasmine|tap|ava)\s|\b(?:cargo|go|deno|dotnet|mvn)\s+test\b|\bgradle\w*\s+test\b/i;
|
|
509
|
+
// #1322 — failure markers in a test runner's own OUTPUT.
|
|
510
|
+
//
|
|
511
|
+
// This is deliberately not an exit-code check: Claude Code's PostToolUse payload
|
|
512
|
+
// carries no exit status, and PostToolUse does not fire at all when a command
|
|
513
|
+
// exits non-zero — so an unmasked red suite already leaves testsRun false, by
|
|
514
|
+
// accident of the hook lifecycle rather than by design. What DOES defeat the
|
|
515
|
+
// gate is a masked exit (`npm test | tail -20`, `npm test || true`,
|
|
516
|
+
// `npm test 2>&1 | grep -i fail`): the pipeline exits 0, PostToolUse fires with
|
|
517
|
+
// a clean-looking response, and a red suite credits the gate. Output is the only
|
|
518
|
+
// signal left, and it is genuinely weaker than a status — see the ticket.
|
|
519
|
+
//
|
|
520
|
+
// Every arm matches a SUMMARY shape a runner emits, never a bare "fail", which
|
|
521
|
+
// occurs constantly in ordinary passing test names ("returns null when the
|
|
522
|
+
// lookup failed"). The count arm excludes an explicit zero so jest's
|
|
523
|
+
// `0 failed, 12 passed` cannot self-block.
|
|
524
|
+
//
|
|
525
|
+
// The count arm's trailing lookahead is what keeps a GREEN run from blocking
|
|
526
|
+
// itself. Mocha's default spec reporter prints every passing test name, so
|
|
527
|
+
// `npm test | tail -20` on a green suite legitimately contains lines like
|
|
528
|
+
// `✓ handles 2 failed retries`. A real summary is followed by a delimiter or a
|
|
529
|
+
// line end (`3 failed | 40 passed`, `1 failed, 2 passed`, `1 failing`), never by
|
|
530
|
+
// more prose — so a lowercase word after the count means it is a sentence, not a
|
|
531
|
+
// tally. `tests`/`test` is exempted because `2 failed tests` is a real summary.
|
|
532
|
+
// Same-line whitespace only: at a line end there is nothing to disqualify.
|
|
533
|
+
var TEST_FAILURE_RE = new RegExp([
|
|
534
|
+
'\\b(?!0\\b)\\d+\\s+(?:tests?\\s+)?(?:failed|failing|failures?)\\b(?![^\\S\\n]+(?!tests?\\b)[a-z])', // vitest/jest/pytest/mocha counts
|
|
535
|
+
'^\\s*(?:FAIL|FAILED)\\b', // vitest + jest per-file, pytest FAILED
|
|
536
|
+
'^\\s*---\\s*FAIL:', // go test
|
|
537
|
+
'\\btest result:\\s*FAILED\\b', // cargo
|
|
538
|
+
'^npm ERR!', // npm wrapper around any of the above
|
|
539
|
+
].join('|'), 'im');
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* #1322 — why a just-fired record-test-run must NOT be credited, or null.
|
|
543
|
+
*
|
|
544
|
+
* Absent output is not evidence of failure: a quiet green `npm test > /dev/null`
|
|
545
|
+
* and a silently-masked red one are indistinguishable, and treating the pair as
|
|
546
|
+
* failures would block every consumer who redirects test output. Absent means
|
|
547
|
+
* unknown, and unknown keeps the pre-#1322 behaviour.
|
|
548
|
+
*/
|
|
549
|
+
function detectTestFailure() {
|
|
550
|
+
if (process.env.TOOL_RESPONSE_interrupted === 'true') return 'the run was interrupted';
|
|
551
|
+
var out = (process.env.TOOL_RESPONSE_stdout || '') + '\n' + (process.env.TOOL_RESPONSE_stderr || '');
|
|
552
|
+
if (!out.trim()) return null;
|
|
553
|
+
var hit = out.match(TEST_FAILURE_RE);
|
|
554
|
+
return hit ? 'output reports "' + hit[0].trim().slice(0, 40) + '"' : null;
|
|
555
|
+
}
|
|
501
556
|
// Edits to these don't change runtime behaviour, so they don't invalidate prior test/simplify runs.
|
|
502
557
|
// Lock files and .gitignore are tracked but inert; package.json/*.yaml ARE source — they reset.
|
|
503
558
|
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;
|
|
@@ -508,7 +563,13 @@ var EDIT_RESET_SKIP_BOTH_RE = /\.(md|markdown|txt|rst|adoc|lock|gitignore)$|(?:^
|
|
|
508
563
|
// they shouldn't reset testsRun/simplifyRun the way a real source edit does.
|
|
509
564
|
// Trailing terminator includes `.` so the single-file template form
|
|
510
565
|
// `.github/PULL_REQUEST_TEMPLATE.md` matches alongside the directory form.
|
|
511
|
-
|
|
566
|
+
// #1348 — `.moflo/` joins them. It is moflo's own gitignored state directory
|
|
567
|
+
// (daemon locks, memory db, SDD spec/plan artifacts), so nothing written there
|
|
568
|
+
// can appear in the branch diff, and a spec edit invalidating the test run is
|
|
569
|
+
// pure noise. Scoped to the reset only — deliberately NOT added to EXEMPT,
|
|
570
|
+
// which would also un-gate reads of `.moflo/specs/**`, and those are indexed
|
|
571
|
+
// guidance that memory-first should still route through a search.
|
|
572
|
+
var EDIT_RESET_SKIP_PATH_RE = /(?:^|[\\\/])\.github[\\\/](?:workflows|ISSUE_TEMPLATE|PULL_REQUEST_TEMPLATE)(?:[\\\/.]|$)|(?:^|[\\\/])\.moflo[\\\/]/i;
|
|
512
573
|
// Test files: invalidate the testing gate (tests are stale once test code changes)
|
|
513
574
|
// but NOT the simplify gate — /simplify already reviewed the production code; touching
|
|
514
575
|
// a test file or fixture doesn't expose new untested surface for code review (#908).
|
|
@@ -637,8 +698,11 @@ function getChangedFilesVsBase() {
|
|
|
637
698
|
|
|
638
699
|
switch (command) {
|
|
639
700
|
case 'check-before-agent': {
|
|
640
|
-
//
|
|
641
|
-
//
|
|
701
|
+
// Mostly advisory. The TaskCreate + memory reminders below go to stdout and
|
|
702
|
+
// never block — their wording must not claim otherwise (#1326). The one
|
|
703
|
+
// exception is the #952 swarm/hive check at the bottom of this case, which
|
|
704
|
+
// writes to stderr and exits 2.
|
|
705
|
+
// Memory-first enforcement otherwise happens at the scan/read gate layer.
|
|
642
706
|
// SubagentStart hook injects guidance directive into subagent context.
|
|
643
707
|
//
|
|
644
708
|
// #931 — TaskCreate REMINDER and the namespace hint moved here from
|
|
@@ -647,7 +711,7 @@ switch (command) {
|
|
|
647
711
|
// consumer.
|
|
648
712
|
var s = readState();
|
|
649
713
|
if (config.task_create_first && !s.tasksCreated) {
|
|
650
|
-
process.stdout.write('REMINDER: Use TaskCreate before spawning agents
|
|
714
|
+
process.stdout.write('REMINDER: Use TaskCreate before spawning agents.\n');
|
|
651
715
|
}
|
|
652
716
|
if (config.memory_first && s.memoryRequired && !s.memorySearched) {
|
|
653
717
|
process.stdout.write('REMINDER: Search memory (mcp__moflo__memory_search) before spawning agents. On chunk hits, traverse via mcp__moflo__memory_get_neighbors — see .claude/guidance/moflo-memory-protocol.md\n');
|
|
@@ -809,8 +873,17 @@ switch (command) {
|
|
|
809
873
|
case 'record-test-run': {
|
|
810
874
|
var cmd = process.env.TOOL_INPUT_command || '';
|
|
811
875
|
if (TEST_RUNNER_RE.test(cmd)) {
|
|
876
|
+
// #1322 — a red run is evidence AGAINST the gate, so it also clears a
|
|
877
|
+
// flag an earlier green run earned. Without the reset, `npm test` (green)
|
|
878
|
+
// followed by an edit and `npm test | tail -20` (red) would leave the
|
|
879
|
+
// gate satisfied by the stale first run — the edit resets testsRun, but
|
|
880
|
+
// the masked red run would immediately set it back.
|
|
881
|
+
var failure = detectTestFailure();
|
|
812
882
|
var s = readState();
|
|
813
|
-
if (
|
|
883
|
+
if (failure) {
|
|
884
|
+
if (s.testsRun) { s.testsRun = false; writeState(s); }
|
|
885
|
+
process.stderr.write('gate: record-test-run not credited — ' + failure + '\n');
|
|
886
|
+
} else if (!s.testsRun) {
|
|
814
887
|
s.testsRun = true;
|
|
815
888
|
writeState(s);
|
|
816
889
|
}
|
|
@@ -865,30 +938,92 @@ switch (command) {
|
|
|
865
938
|
// crediting them would let the gate pass without an end-to-end verify.
|
|
866
939
|
if (vName === 'verify') {
|
|
867
940
|
var s = readState();
|
|
868
|
-
|
|
941
|
+
// #1332: invoking /verify starts a verification; it does not conclude
|
|
942
|
+
// one. Clear any prior verdict so the run in progress cannot inherit the
|
|
943
|
+
// PASS from a previous issue and satisfy check-before-done on its own.
|
|
944
|
+
if (!s.verifyRun || s.verifyOutcome) {
|
|
945
|
+
s.verifyRun = true;
|
|
946
|
+
s.verifyOutcome = null;
|
|
947
|
+
writeState(s);
|
|
948
|
+
}
|
|
869
949
|
} else if (vName) {
|
|
870
950
|
process.stderr.write('gate: record-verify-run no-op — TOOL_INPUT_skill="' + vName + '" is not verify\n');
|
|
871
951
|
}
|
|
872
952
|
break;
|
|
873
953
|
}
|
|
954
|
+
case 'record-verify-outcome': {
|
|
955
|
+
// #1332. Fires PostToolUse on mcp__moflo__memory_store. `record-verify-run`
|
|
956
|
+
// above proves a verification was ATTEMPTED; this proves how it ENDED.
|
|
957
|
+
//
|
|
958
|
+
// The verdict is read from the structured record #1328 made /verify write
|
|
959
|
+
// to memory_store's `metadata` — never parsed out of the prose `value`,
|
|
960
|
+
// which is precisely the free-text dependency #1328 removed. gate-hook.mjs
|
|
961
|
+
// forwards the object as JSON (see its MAX_STRUCTURED_LEN note).
|
|
962
|
+
var mKey = process.env.TOOL_INPUT_key || '';
|
|
963
|
+
if (mKey.indexOf('verify:') !== 0) break;
|
|
964
|
+
var rawMeta = process.env.TOOL_INPUT_metadata || '';
|
|
965
|
+
if (!rawMeta) {
|
|
966
|
+
process.stderr.write('gate: record-verify-outcome — "' + mKey + '" carries no metadata; verdict not recorded\n');
|
|
967
|
+
break;
|
|
968
|
+
}
|
|
969
|
+
var parsedMeta = null;
|
|
970
|
+
try { parsedMeta = JSON.parse(rawMeta); } catch (e) { parsedMeta = null; }
|
|
971
|
+
if (!parsedMeta || typeof parsedMeta !== 'object' || parsedMeta.type !== 'verify-record') break;
|
|
972
|
+
var overall = typeof parsedMeta.overall === 'string' ? parsedMeta.overall.toUpperCase() : '';
|
|
973
|
+
if (overall !== 'PASS' && overall !== 'FAIL' && overall !== 'UNVERIFIED') {
|
|
974
|
+
process.stderr.write('gate: record-verify-outcome — unrecognised overall="' + parsedMeta.overall + '"; treating as not-passing\n');
|
|
975
|
+
overall = 'UNVERIFIED';
|
|
976
|
+
}
|
|
977
|
+
var vs = readState();
|
|
978
|
+
// #1348 — refuse a verdict for a run that is no longer live. `verifyRun`
|
|
979
|
+
// false here means a code edit fired reset-edit-gates between the /verify
|
|
980
|
+
// invocation and this store, so the verdict describes pre-edit code.
|
|
981
|
+
// Recording it anyway produced the contradictory `verifyRun:false,
|
|
982
|
+
// verifyOutcome:'PASS'` state that check-before-done's four-way message
|
|
983
|
+
// chain has no branch for — it reports "a code edit invalidated the previous
|
|
984
|
+
// verification" while a PASS sits in state, which reads as a gate bug.
|
|
985
|
+
if (!vs.verifyRun) {
|
|
986
|
+
process.stderr.write('gate: record-verify-outcome — "' + mKey + '" arrived after a code edit invalidated the run; verdict not recorded (re-run /verify)\n');
|
|
987
|
+
break;
|
|
988
|
+
}
|
|
989
|
+
vs.verifyOutcome = overall;
|
|
990
|
+
writeState(vs);
|
|
991
|
+
break;
|
|
992
|
+
}
|
|
874
993
|
case 'reset-edit-gates': {
|
|
875
994
|
var fp = process.env.TOOL_INPUT_file_path || '';
|
|
876
995
|
// Inert files (markdown, lockfiles, CHANGELOG, .env.example) AND inert paths
|
|
877
996
|
// (.github/workflows/, .github/ISSUE_TEMPLATE/, .github/PULL_REQUEST_TEMPLATE/, #1176):
|
|
878
997
|
// no gate reset — editing these doesn't expose new runtime surface.
|
|
879
998
|
if (fp && (EDIT_RESET_SKIP_BOTH_RE.test(fp) || EDIT_RESET_SKIP_PATH_RE.test(fp))) break;
|
|
999
|
+
// #1348 — a scratchpad write under the OS temp dir is transient tool I/O and
|
|
1000
|
+
// can never reach the branch diff, but it used to reset tests + simplify +
|
|
1001
|
+
// verify like any source change: a /verify run that jotted a probe cleared
|
|
1002
|
+
// the /flo-simplify stamp, and the two gates invalidated each other with no
|
|
1003
|
+
// ordering that satisfied both. Reuses the predicate the memory-first gate
|
|
1004
|
+
// already trusts for these paths (#1294) rather than a second one.
|
|
1005
|
+
// Scope: tmp-only, NOT project-root containment — so a project rooted under
|
|
1006
|
+
// tmp has its own edits skipped too (pinned in the #1348 tests). Containment
|
|
1007
|
+
// is the more general rule, but it fails OPEN when the root can't be
|
|
1008
|
+
// resolved, and a gate that silently stops resetting is the worse failure.
|
|
1009
|
+
if (isEphemeralPath(fp)) break;
|
|
880
1010
|
var s = readState();
|
|
881
1011
|
// Test-only edits invalidate testsRun but preserve simplifyRun (#908).
|
|
882
1012
|
var isTestOnly = fp && EDIT_RESET_SKIP_SIMPLIFY_ONLY_RE.test(fp);
|
|
883
1013
|
var resetTests = s.testsRun;
|
|
884
1014
|
// A code edit invalidates a prior verification (Story #1274) — same as tests,
|
|
885
1015
|
// including test-only edits (the criteria being verified may have moved).
|
|
886
|
-
|
|
1016
|
+
// #1332: also fires when a verdict lingers without the flag, so no path
|
|
1017
|
+
// can leave a recorded outcome behind after a source edit.
|
|
1018
|
+
var resetVerify = s.verifyRun || !!s.verifyOutcome;
|
|
887
1019
|
var resetSimplify = s.simplifyRun && !isTestOnly;
|
|
888
1020
|
if (!resetTests && !resetSimplify && !resetVerify) break;
|
|
889
1021
|
var gates = [];
|
|
890
1022
|
if (resetTests) { s.testsRun = false; gates.push('tests'); }
|
|
891
|
-
|
|
1023
|
+
// #1332: drop the recorded verdict with the flag. Leaving a stale PASS
|
|
1024
|
+
// behind would let the next check-before-done pass on a verdict that
|
|
1025
|
+
// describes pre-edit code.
|
|
1026
|
+
if (resetVerify) { s.verifyRun = false; s.verifyOutcome = null; gates.push('verify'); }
|
|
892
1027
|
if (resetSimplify) { s.simplifyRun = false; gates.push('simplify'); }
|
|
893
1028
|
if (fp) {
|
|
894
1029
|
s.lastResetBy = { file: fp, at: new Date().toISOString(), gates: gates };
|
|
@@ -978,7 +1113,7 @@ switch (command) {
|
|
|
978
1113
|
}
|
|
979
1114
|
}
|
|
980
1115
|
var missing = [];
|
|
981
|
-
if (config.testing_gate && !s.testsRun) missing.push('tests have not run since the last code edit (run npm test, vitest, jest, pytest, or similar)');
|
|
1116
|
+
if (config.testing_gate && !s.testsRun) missing.push('tests have not run green since the last code edit (run npm test, vitest, jest, pytest, or similar — a run whose output reports failures does not count, #1322)');
|
|
982
1117
|
if (config.simplify_gate && !s.simplifyRun) missing.push('/flo-simplify (or /distill) has not run since the last code edit');
|
|
983
1118
|
if (config.learnings_gate && !s.learningsStored) missing.push('learnings have not been stored (call mcp__moflo__memory_store)');
|
|
984
1119
|
if (missing.length === 0) break;
|
|
@@ -989,6 +1124,10 @@ switch (command) {
|
|
|
989
1124
|
if (s.lastResetBy && s.lastResetBy.file) {
|
|
990
1125
|
process.stderr.write('Last gate reset: ' + s.lastResetBy.file + ' (' + (s.lastResetBy.gates || []).join(', ') + ')\n');
|
|
991
1126
|
}
|
|
1127
|
+
// #1348 — name the order, not just the missing gate. Satisfying one gate can
|
|
1128
|
+
// invalidate another (/flo-simplify edits code, which resets tests + verify),
|
|
1129
|
+
// so "what is missing" alone left callers rediscovering the sequence by trial.
|
|
1130
|
+
process.stderr.write(ORDER_HINT);
|
|
992
1131
|
process.stderr.write('Disable per-gate via moflo.yaml:\n');
|
|
993
1132
|
process.stderr.write(' gates:\n testing_gate: false\n simplify_gate: false\n learnings_gate: false\n');
|
|
994
1133
|
process.exit(2);
|
|
@@ -1017,12 +1156,34 @@ switch (command) {
|
|
|
1017
1156
|
}
|
|
1018
1157
|
}
|
|
1019
1158
|
var sd = readState();
|
|
1020
|
-
|
|
1159
|
+
// #1332: gate on the OUTCOME, not on attendance. Before this, `verifyRun`
|
|
1160
|
+
// alone opened the gate, so a /verify returning FAIL satisfied it exactly
|
|
1161
|
+
// as a PASS did — a failing verdict is still a successful tool invocation.
|
|
1162
|
+
if (sd.verifyRun && sd.verifyOutcome === 'PASS') break;
|
|
1021
1163
|
process.stderr.write('BLOCKED: gh pr create requires verification before done:\n');
|
|
1022
|
-
|
|
1023
|
-
|
|
1164
|
+
// The four states need different remedies, so name which one applies
|
|
1165
|
+
// rather than emitting one message that fits none of them.
|
|
1166
|
+
var invalidated = sd.lastResetBy && sd.lastResetBy.file
|
|
1167
|
+
&& (sd.lastResetBy.gates || []).indexOf('verify') >= 0;
|
|
1168
|
+
if (!sd.verifyRun && invalidated) {
|
|
1169
|
+
process.stderr.write(' - a code edit invalidated the previous verification — re-run /verify\n');
|
|
1024
1170
|
process.stderr.write('Last gate reset: ' + sd.lastResetBy.file + ' (verify)\n');
|
|
1171
|
+
} else if (!sd.verifyRun) {
|
|
1172
|
+
process.stderr.write(' - the change has not been verified since the last code edit (run /verify)\n');
|
|
1173
|
+
} else if (sd.verifyOutcome === 'FAIL' || sd.verifyOutcome === 'UNVERIFIED') {
|
|
1174
|
+
process.stderr.write(' - /verify ran and returned ' + sd.verifyOutcome + ' — fix the failing criteria, then re-run /verify\n');
|
|
1175
|
+
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');
|
|
1176
|
+
} else {
|
|
1177
|
+
// Ran, but no verdict reached the gate: /verify was invoked and never
|
|
1178
|
+
// recorded a structured outcome (interrupted, or it stored prose only).
|
|
1179
|
+
process.stderr.write(' - /verify ran but recorded no verdict — re-run it so it stores a structured result\n');
|
|
1180
|
+
process.stderr.write(' (Step 5 of the verify skill must pass metadata.overall to memory_store)\n');
|
|
1181
|
+
// #1348 — the trap this state sets: re-invoking /verify CLEARS any prior
|
|
1182
|
+
// verdict by design (#1332), so the obvious recovery lands right back here
|
|
1183
|
+
// unless Step 5 completes. Say so, rather than letting it be rediscovered.
|
|
1184
|
+
process.stderr.write(' Re-invoking /verify clears the prior verdict, so a re-run that skips Step 5 lands here again.\n');
|
|
1025
1185
|
}
|
|
1186
|
+
process.stderr.write(ORDER_HINT);
|
|
1026
1187
|
process.stderr.write('Disable via moflo.yaml:\n');
|
|
1027
1188
|
process.stderr.write(' gates:\n verify_before_done: false\n');
|
|
1028
1189
|
process.exit(2);
|
|
File without changes
|
|
File without changes
|
|
@@ -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
|
|