@claude-flow/cli 3.72.2 → 3.74.0
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/helpers/.helpers-version +1 -1
- package/.claude/helpers/auto-memory-hook.mjs +53 -224
- package/.claude/helpers/helpers.manifest.json +6 -6
- package/.claude/helpers/hook-handler.cjs +10 -89
- package/.claude/helpers/intelligence.cjs +1 -23
- package/.claude/helpers/statusline.cjs +14 -29
- package/README.md +29 -21
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/agent.js +2 -0
- package/dist/src/commands/doctor.js +5 -3
- package/dist/src/commands/funnel.d.ts +1 -0
- package/dist/src/commands/funnel.js +37 -2
- package/dist/src/commands/hooks.js +16 -2
- package/dist/src/commands/init.js +70 -16
- package/dist/src/commands/mcp.js +15 -0
- package/dist/src/commands/swarm.js +2 -0
- package/dist/src/funnel/activation.d.ts +30 -0
- package/dist/src/funnel/activation.js +69 -0
- package/dist/src/funnel/events.js +4 -1
- package/dist/src/funnel/index.d.ts +2 -1
- package/dist/src/funnel/index.js +2 -1
- package/dist/src/funnel/message-transport.d.ts +12 -0
- package/dist/src/funnel/message-transport.js +23 -1
- package/dist/src/funnel/types.d.ts +2 -2
- package/dist/src/init/first-win.d.ts +24 -0
- package/dist/src/init/first-win.js +97 -0
- package/dist/src/mcp-tools/agent-tools.js +2 -0
- package/dist/src/mcp-tools/memory-tools.js +17 -0
- package/dist/src/mcp-tools/metaharness-tools.js +19 -0
- package/dist/src/mcp-tools/swarm-tools.js +2 -0
- package/dist/src/memory/memory-bridge.d.ts +2 -0
- package/dist/src/memory/memory-bridge.js +1 -1
- package/dist/src/memory/memory-initializer.d.ts +2 -0
- package/dist/src/memory/memory-initializer.js +1 -1
- package/dist/src/metaharness/darwin-lib.d.ts +68 -0
- package/dist/src/metaharness/darwin-lib.js +150 -0
- package/dist/src/transfer/store/download.d.ts +1 -1
- package/dist/src/transfer/store/download.js +22 -21
- package/dist/src/transfer/store/publish.d.ts +1 -1
- package/dist/src/transfer/store/publish.js +13 -14
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +5 -5
- package/node_modules/@claude-flow/security/dist/input-validator.d.ts +138 -106
- package/node_modules/@claude-flow/security/dist/input-validator.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/input-validator.js +23 -26
- package/node_modules/@claude-flow/security/dist/input-validator.js.map +1 -1
- package/node_modules/@claude-flow/security/package.json +2 -2
- package/package.json +10 -10
- package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +1 -1
- package/plugins/ruflo-metaharness/CHANGELOG.md +3 -0
- package/plugins/ruflo-metaharness/scripts/_darwin.mjs +13 -0
- package/plugins/ruflo-metaharness/scripts/bench.mjs +34 -2
- package/plugins/ruflo-metaharness/scripts/smoke.sh +4 -4
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activation funnel (ruflo adoption plan; ADR-309 opt-in telemetry).
|
|
3
|
+
*
|
|
4
|
+
* installed -> init_completed -> first_agent_run -> session_active (7-day retention)
|
|
5
|
+
*
|
|
6
|
+
* Every event goes through recordFunnelEvent, which records nothing without
|
|
7
|
+
* an explicit, current `telemetry` consent receipt. The once-ever and
|
|
8
|
+
* once-per-day markers live in their own state file because a successful
|
|
9
|
+
* flush removes sent events from the queue (lastRecordedEvent would forget).
|
|
10
|
+
*/
|
|
11
|
+
import { getConsent } from './consent.js';
|
|
12
|
+
import { recordFunnelEvent } from './events.js';
|
|
13
|
+
import { isCI, isInteractive } from './environment.js';
|
|
14
|
+
import { resolveFunnelEnabled } from './precedence.js';
|
|
15
|
+
import { readStateJson, writeStateJson } from './state.js';
|
|
16
|
+
const MARKERS_FILE = 'funnel-activation.json';
|
|
17
|
+
const day = (now) => now.toISOString().slice(0, 10);
|
|
18
|
+
function markers() {
|
|
19
|
+
return readStateJson(MARKERS_FILE) ?? {};
|
|
20
|
+
}
|
|
21
|
+
/** `ruflo init` finished in a project. Recorded on every successful init. */
|
|
22
|
+
export function recordInitCompleted(release, now = new Date()) {
|
|
23
|
+
return recordFunnelEvent('init_completed', 'init', release, now);
|
|
24
|
+
}
|
|
25
|
+
/** The first agent spawn or swarm init on this machine. Recorded once, ever. */
|
|
26
|
+
export function recordFirstAgentRun(release, surface = 'cli', now = new Date()) {
|
|
27
|
+
const m = markers();
|
|
28
|
+
if (m.firstAgentRunAt)
|
|
29
|
+
return false;
|
|
30
|
+
if (!recordFunnelEvent('first_agent_run', surface, release, now))
|
|
31
|
+
return false;
|
|
32
|
+
writeStateJson(MARKERS_FILE, { ...m, firstAgentRunAt: now.toISOString() });
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
/** At most one "active today" marker per calendar day (UTC). */
|
|
36
|
+
export function recordSessionActive(release, now = new Date()) {
|
|
37
|
+
const m = markers();
|
|
38
|
+
const today = day(now);
|
|
39
|
+
if (m.lastActiveDay === today)
|
|
40
|
+
return false;
|
|
41
|
+
if (!recordFunnelEvent('session_active', 'cli', release, now))
|
|
42
|
+
return false;
|
|
43
|
+
writeStateJson(MARKERS_FILE, { ...m, lastActiveDay: today });
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Whether `ruflo init` should ask the one-time telemetry question: an
|
|
48
|
+
* interactive terminal, not CI, funnel not disabled, no telemetry receipt
|
|
49
|
+
* yet (asked once, whatever the answer), and not suppressed by flag.
|
|
50
|
+
*/
|
|
51
|
+
export function shouldOfferTelemetry(opts = {}) {
|
|
52
|
+
const env = opts.env ?? process.env;
|
|
53
|
+
if (opts.noTelemetryPrompt)
|
|
54
|
+
return false;
|
|
55
|
+
if (isCI(env))
|
|
56
|
+
return false;
|
|
57
|
+
if (!isInteractive())
|
|
58
|
+
return false;
|
|
59
|
+
if (!resolveFunnelEnabled(opts.cwd ?? process.cwd(), env).enabled)
|
|
60
|
+
return false;
|
|
61
|
+
return getConsent('telemetry').at === null;
|
|
62
|
+
}
|
|
63
|
+
/** Fire-and-forget wrapper for command handlers: never throws, never blocks. */
|
|
64
|
+
export function noteFirstAgentRun(surface = 'cli') {
|
|
65
|
+
void import('../mcp-protocol.js')
|
|
66
|
+
.then(({ rufloPackageVersion }) => { recordFirstAgentRun(rufloPackageVersion(), surface); })
|
|
67
|
+
.catch(() => undefined);
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=activation.js.map
|
|
@@ -39,8 +39,11 @@ const EVENT_NAMES = [
|
|
|
39
39
|
'training_share_disabled',
|
|
40
40
|
'advisor_tip_enabled',
|
|
41
41
|
'advisor_tip_disabled',
|
|
42
|
+
'init_completed',
|
|
43
|
+
'first_agent_run',
|
|
44
|
+
'session_active',
|
|
42
45
|
];
|
|
43
|
-
const SURFACES = ['statusline', 'init', 'credit_exhaustion'];
|
|
46
|
+
const SURFACES = ['statusline', 'init', 'credit_exhaustion', 'cli'];
|
|
44
47
|
/** Rotate the pseudonymous ID every 90 days (ADR-305). */
|
|
45
48
|
const FUNNEL_ID_TTL_MS = 90 * 24 * 60 * 60 * 1000;
|
|
46
49
|
/**
|
|
@@ -9,7 +9,8 @@ export { resolveFunnelEnabled } from './precedence.js';
|
|
|
9
9
|
export { CONSENT_DOMAINS, getConsent, hasConsent, readConsents, recordConsent, revokeConsent, } from './consent.js';
|
|
10
10
|
export { DISCLOSURE_GRACE_MS, DISCLOSURE_ROTATION_SLOT_MS, getDisclosure, promoEligible, recordDisclosureAccepted, recordDisclosureDeclined, recordDisclosureReenabled, recordDisclosureShown, selectDisclosureMessage, } from './disclosure.js';
|
|
11
11
|
export { MAX_MESSAGE_COLUMNS, MESSAGES, containsForbiddenSequences, displayWidth, eligibleMessages, eligibleMessagesFromPools, isAllowedUrl, isValidMessage, } from './messages.js';
|
|
12
|
-
export { DEFAULT_MESSAGES_ENDPOINT, getRemoteMessages, refreshRemoteMessages, } from './message-transport.js';
|
|
12
|
+
export { DEFAULT_MESSAGES_ENDPOINT, getRemoteMessages, funnelUserAgent, launchSurface, refreshRemoteMessages, } from './message-transport.js';
|
|
13
|
+
export { flushEvents } from './event-transport.js';
|
|
13
14
|
export { PROMO_REPEAT_CAP_MS, PROMO_SLOT_MODULO, ROTATION_SLOT_MS, selectMessage, } from './rotation.js';
|
|
14
15
|
export { CREDIT_RECOVERY_HINT, classifyCreditError, renderCreditRecovery, shouldShowCreditRecovery, type CreditPromptSession, type ProviderErrorLike, } from './credit-errors.js';
|
|
15
16
|
export { deleteFunnelData, getFunnelId, lastRecordedEvent, recordFunnelEvent } from './events.js';
|
package/dist/src/funnel/index.js
CHANGED
|
@@ -9,7 +9,8 @@ export { resolveFunnelEnabled } from './precedence.js';
|
|
|
9
9
|
export { CONSENT_DOMAINS, getConsent, hasConsent, readConsents, recordConsent, revokeConsent, } from './consent.js';
|
|
10
10
|
export { DISCLOSURE_GRACE_MS, DISCLOSURE_ROTATION_SLOT_MS, getDisclosure, promoEligible, recordDisclosureAccepted, recordDisclosureDeclined, recordDisclosureReenabled, recordDisclosureShown, selectDisclosureMessage, } from './disclosure.js';
|
|
11
11
|
export { MAX_MESSAGE_COLUMNS, MESSAGES, containsForbiddenSequences, displayWidth, eligibleMessages, eligibleMessagesFromPools, isAllowedUrl, isValidMessage, } from './messages.js';
|
|
12
|
-
export { DEFAULT_MESSAGES_ENDPOINT, getRemoteMessages, refreshRemoteMessages, } from './message-transport.js';
|
|
12
|
+
export { DEFAULT_MESSAGES_ENDPOINT, getRemoteMessages, funnelUserAgent, launchSurface, refreshRemoteMessages, } from './message-transport.js';
|
|
13
|
+
export { flushEvents } from './event-transport.js';
|
|
13
14
|
export { PROMO_REPEAT_CAP_MS, PROMO_SLOT_MODULO, ROTATION_SLOT_MS, selectMessage, } from './rotation.js';
|
|
14
15
|
export { CREDIT_RECOVERY_HINT, classifyCreditError, renderCreditRecovery, shouldShowCreditRecovery, } from './credit-errors.js';
|
|
15
16
|
export { deleteFunnelData, getFunnelId, lastRecordedEvent, recordFunnelEvent } from './events.js';
|
|
@@ -31,6 +31,18 @@
|
|
|
31
31
|
* the trust boundary. All content is treated as untrusted regardless.
|
|
32
32
|
*/
|
|
33
33
|
import type { FunnelMessage } from './types.js';
|
|
34
|
+
/**
|
|
35
|
+
* User-Agent for the messages fetch — ADR-309 operational telemetry (see
|
|
36
|
+
* docs/privacy/funnel-legitimate-interest-assessment.md). Only the CLI
|
|
37
|
+
* release and the coarse OS/CPU family, so the server can count releases
|
|
38
|
+
* and platforms. No identifier, path, hostname, or account: the
|
|
39
|
+
* `ruflo-funnel/messages` prefix stays first so existing log filters match.
|
|
40
|
+
* A trailing `surface/plugin` marks a fetch made by the ruflo-core plugin's
|
|
41
|
+
* MCP launch, so plugin-only users (who never run `init`) can be counted.
|
|
42
|
+
*/
|
|
43
|
+
export declare function funnelUserAgent(version?: string, platform?: string, arch?: string, surface?: string | undefined): string;
|
|
44
|
+
/** The surface set by a launcher (plugins/ruflo-core/scripts/mcp-launch.cjs), if any. */
|
|
45
|
+
export declare function launchSurface(env?: NodeJS.ProcessEnv): string | undefined;
|
|
34
46
|
/** Endpoint the client hits — overridable for staging / self-hosted. */
|
|
35
47
|
export declare const DEFAULT_MESSAGES_ENDPOINT: string;
|
|
36
48
|
/**
|
|
@@ -34,7 +34,29 @@ import * as https from 'https';
|
|
|
34
34
|
import { URL as NodeUrl } from 'url';
|
|
35
35
|
import { isValidMessage } from './messages.js';
|
|
36
36
|
import { readStateJson, writeStateJson } from './state.js';
|
|
37
|
+
import { rufloPackageVersion } from '../mcp-protocol.js';
|
|
37
38
|
const CACHE_FILE = 'funnel-messages-cache.json';
|
|
39
|
+
/**
|
|
40
|
+
* User-Agent for the messages fetch — ADR-309 operational telemetry (see
|
|
41
|
+
* docs/privacy/funnel-legitimate-interest-assessment.md). Only the CLI
|
|
42
|
+
* release and the coarse OS/CPU family, so the server can count releases
|
|
43
|
+
* and platforms. No identifier, path, hostname, or account: the
|
|
44
|
+
* `ruflo-funnel/messages` prefix stays first so existing log filters match.
|
|
45
|
+
* A trailing `surface/plugin` marks a fetch made by the ruflo-core plugin's
|
|
46
|
+
* MCP launch, so plugin-only users (who never run `init`) can be counted.
|
|
47
|
+
*/
|
|
48
|
+
export function funnelUserAgent(version = rufloPackageVersion(), platform = process.platform, arch = process.arch, surface = launchSurface()) {
|
|
49
|
+
const clean = (v) => v.replace(/[^A-Za-z0-9._-]/g, '').slice(0, 32) || 'unknown';
|
|
50
|
+
const tail = surface ? ` surface/${surface}` : '';
|
|
51
|
+
return `ruflo-funnel/messages ruflo/${clean(version)} (${clean(platform)}; ${clean(arch)})${tail}`;
|
|
52
|
+
}
|
|
53
|
+
/** Launch surfaces the UA may name. Closed set: an env value outside it is dropped. */
|
|
54
|
+
const LAUNCH_SURFACES = new Set(['plugin']);
|
|
55
|
+
/** The surface set by a launcher (plugins/ruflo-core/scripts/mcp-launch.cjs), if any. */
|
|
56
|
+
export function launchSurface(env = process.env) {
|
|
57
|
+
const v = env.RUFLO_LAUNCH_SURFACE;
|
|
58
|
+
return v && LAUNCH_SURFACES.has(v) ? v : undefined;
|
|
59
|
+
}
|
|
38
60
|
const CACHE_TTL_MS = 6 * 60 * 60 * 1000; // 6h — matches server Cache-Control
|
|
39
61
|
const CACHE_MAX_BYTES = 128 * 1024;
|
|
40
62
|
const CACHE_MAX_MESSAGES = 200;
|
|
@@ -79,7 +101,7 @@ function httpsGet(url) {
|
|
|
79
101
|
path: target.pathname + target.search,
|
|
80
102
|
method: 'GET',
|
|
81
103
|
headers: {
|
|
82
|
-
'User-Agent':
|
|
104
|
+
'User-Agent': funnelUserAgent(),
|
|
83
105
|
'Accept': 'application/json',
|
|
84
106
|
},
|
|
85
107
|
}, (res) => {
|
|
@@ -77,8 +77,8 @@ export interface NormalizedCreditError {
|
|
|
77
77
|
/** Original provider error, preserved verbatim for --verbose paths. */
|
|
78
78
|
cause?: unknown;
|
|
79
79
|
}
|
|
80
|
-
export type FunnelEventName = 'disclosure_shown' | 'funnel_disabled' | 'signup_opened' | 'account_created' | 'proxy_activated' | 'promo_impression' | 'promo_open' | 'sponsor_mode_enabled' | 'sponsor_mode_disabled' | 'sponsor_capacity_exhausted' | 'power_saver_enabled' | 'power_saver_disabled' | 'toggle_cooldown_blocked' | 'training_share_enabled' | 'training_share_disabled' | 'advisor_tip_enabled' | 'advisor_tip_disabled';
|
|
81
|
-
export type FunnelSurface = 'statusline' | 'init' | 'credit_exhaustion';
|
|
80
|
+
export type FunnelEventName = 'disclosure_shown' | 'funnel_disabled' | 'signup_opened' | 'account_created' | 'proxy_activated' | 'promo_impression' | 'promo_open' | 'sponsor_mode_enabled' | 'sponsor_mode_disabled' | 'sponsor_capacity_exhausted' | 'power_saver_enabled' | 'power_saver_disabled' | 'toggle_cooldown_blocked' | 'training_share_enabled' | 'training_share_disabled' | 'advisor_tip_enabled' | 'advisor_tip_disabled' | 'init_completed' | 'first_agent_run' | 'session_active';
|
|
81
|
+
export type FunnelSurface = 'statusline' | 'init' | 'credit_exhaustion' | 'cli';
|
|
82
82
|
export interface FunnelEvent {
|
|
83
83
|
schemaVersion: 1;
|
|
84
84
|
event: FunnelEventName;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The concrete result `ruflo init` ends with (adoption plan item 1): a quick,
|
|
3
|
+
* local snapshot of the user's own project, plus one prompt to paste into
|
|
4
|
+
* Claude Code. Replaces a list of commands with something about *their* repo.
|
|
5
|
+
*
|
|
6
|
+
* Local only: `git` with argument arrays (no shell), a hard timeout per call,
|
|
7
|
+
* no network. Any failure returns null and init prints nothing extra.
|
|
8
|
+
*/
|
|
9
|
+
export interface ProjectSnapshot {
|
|
10
|
+
files: number;
|
|
11
|
+
languages: string[];
|
|
12
|
+
hotFile?: {
|
|
13
|
+
path: string;
|
|
14
|
+
changes: number;
|
|
15
|
+
};
|
|
16
|
+
testCommand?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Snapshot of the project at `cwd`, or null when it is not a git repo with code. */
|
|
19
|
+
export declare function snapshotProject(cwd: string, timeoutMs?: number): ProjectSnapshot | null;
|
|
20
|
+
/** The one prompt to paste into Claude Code, specific to this project. */
|
|
21
|
+
export declare function firstPrompt(s: ProjectSnapshot | null): string;
|
|
22
|
+
/** Lines for init's closing summary. Empty when there is nothing useful to say. */
|
|
23
|
+
export declare function snapshotLines(s: ProjectSnapshot | null): string[];
|
|
24
|
+
//# sourceMappingURL=first-win.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The concrete result `ruflo init` ends with (adoption plan item 1): a quick,
|
|
3
|
+
* local snapshot of the user's own project, plus one prompt to paste into
|
|
4
|
+
* Claude Code. Replaces a list of commands with something about *their* repo.
|
|
5
|
+
*
|
|
6
|
+
* Local only: `git` with argument arrays (no shell), a hard timeout per call,
|
|
7
|
+
* no network. Any failure returns null and init prints nothing extra.
|
|
8
|
+
*/
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { extname, join } from 'node:path';
|
|
12
|
+
const LANGUAGE = {
|
|
13
|
+
'.ts': 'TypeScript', '.tsx': 'TypeScript', '.js': 'JavaScript', '.jsx': 'JavaScript', '.mjs': 'JavaScript', '.cjs': 'JavaScript',
|
|
14
|
+
'.py': 'Python', '.rs': 'Rust', '.go': 'Go', '.java': 'Java', '.kt': 'Kotlin', '.rb': 'Ruby', '.php': 'PHP',
|
|
15
|
+
'.cs': 'C#', '.cpp': 'C++', '.cc': 'C++', '.c': 'C', '.h': 'C', '.swift': 'Swift', '.scala': 'Scala',
|
|
16
|
+
'.vue': 'Vue', '.svelte': 'Svelte', '.dart': 'Dart', '.ex': 'Elixir', '.exs': 'Elixir', '.lua': 'Lua',
|
|
17
|
+
};
|
|
18
|
+
// Files a "riskiest module" suggestion should never point at.
|
|
19
|
+
const NOT_CODE = /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|Cargo\.lock|go\.sum|CHANGELOG[^/]*|README[^/]*|\.github\/|\.claude\/|\.claude-flow\/|dist\/|build\/|node_modules\/)/i;
|
|
20
|
+
function git(cwd, args, timeoutMs) {
|
|
21
|
+
const r = spawnSync('git', args, { cwd, encoding: 'utf8', timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
22
|
+
return r.status === 0 && typeof r.stdout === 'string' ? r.stdout : null;
|
|
23
|
+
}
|
|
24
|
+
function testCommandFor(cwd) {
|
|
25
|
+
try {
|
|
26
|
+
const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'));
|
|
27
|
+
const t = pkg.scripts?.test;
|
|
28
|
+
if (t && !/no test specified/i.test(t))
|
|
29
|
+
return 'npm test';
|
|
30
|
+
}
|
|
31
|
+
catch { /* not a node project */ }
|
|
32
|
+
if (existsSync(join(cwd, 'Cargo.toml')))
|
|
33
|
+
return 'cargo test';
|
|
34
|
+
if (existsSync(join(cwd, 'go.mod')))
|
|
35
|
+
return 'go test ./...';
|
|
36
|
+
if (existsSync(join(cwd, 'pyproject.toml')) || existsSync(join(cwd, 'pytest.ini')))
|
|
37
|
+
return 'pytest';
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
/** Snapshot of the project at `cwd`, or null when it is not a git repo with code. */
|
|
41
|
+
export function snapshotProject(cwd, timeoutMs = 2000) {
|
|
42
|
+
try {
|
|
43
|
+
const listed = git(cwd, ['ls-files', '-z'], timeoutMs);
|
|
44
|
+
if (!listed)
|
|
45
|
+
return null;
|
|
46
|
+
const files = listed.split('\0').filter(Boolean);
|
|
47
|
+
if (files.length === 0)
|
|
48
|
+
return null;
|
|
49
|
+
const counts = new Map();
|
|
50
|
+
for (const f of files) {
|
|
51
|
+
const lang = LANGUAGE[extname(f).toLowerCase()];
|
|
52
|
+
if (lang)
|
|
53
|
+
counts.set(lang, (counts.get(lang) ?? 0) + 1);
|
|
54
|
+
}
|
|
55
|
+
const languages = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([l]) => l);
|
|
56
|
+
// Most-changed code file in the last 90 days (bounded to 500 commits).
|
|
57
|
+
let hotFile;
|
|
58
|
+
const log = git(cwd, ['log', '--since=90.days', '--max-count=500', '--name-only', '--pretty=format:'], timeoutMs);
|
|
59
|
+
if (log) {
|
|
60
|
+
const tracked = new Set(files);
|
|
61
|
+
const churn = new Map();
|
|
62
|
+
for (const line of log.split('\n')) {
|
|
63
|
+
const p = line.trim();
|
|
64
|
+
if (!p || NOT_CODE.test(p) || !tracked.has(p) || !LANGUAGE[extname(p).toLowerCase()])
|
|
65
|
+
continue;
|
|
66
|
+
churn.set(p, (churn.get(p) ?? 0) + 1);
|
|
67
|
+
}
|
|
68
|
+
const top = [...churn.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
69
|
+
if (top && top[1] >= 2)
|
|
70
|
+
hotFile = { path: top[0], changes: top[1] };
|
|
71
|
+
}
|
|
72
|
+
return { files: files.length, languages, hotFile, testCommand: testCommandFor(cwd) };
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** The one prompt to paste into Claude Code, specific to this project. */
|
|
79
|
+
export function firstPrompt(s) {
|
|
80
|
+
if (s?.hotFile) {
|
|
81
|
+
const verify = s.testCommand ? ` and run \`${s.testCommand}\` to prove it` : '';
|
|
82
|
+
return `Use a ruflo swarm to review ${s.hotFile.path} (the most-changed file here): find the riskiest logic, add tests for it${verify}, and summarise what you changed.`;
|
|
83
|
+
}
|
|
84
|
+
return 'Use a ruflo swarm to review this repo: find the riskiest module, write tests for it, and summarise what you changed.';
|
|
85
|
+
}
|
|
86
|
+
/** Lines for init's closing summary. Empty when there is nothing useful to say. */
|
|
87
|
+
export function snapshotLines(s) {
|
|
88
|
+
if (!s)
|
|
89
|
+
return [];
|
|
90
|
+
const lines = [`${s.files.toLocaleString('en-US')} tracked file${s.files === 1 ? '' : 's'}${s.languages.length ? ` · ${s.languages.join(', ')}` : ''}`];
|
|
91
|
+
if (s.hotFile)
|
|
92
|
+
lines.push(`Most-changed file (90 days): ${s.hotFile.path} (${s.hotFile.changes} commits)`);
|
|
93
|
+
if (s.testCommand)
|
|
94
|
+
lines.push(`Tests: ${s.testCommand}`);
|
|
95
|
+
return lines;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=first-win.js.map
|
|
@@ -12,6 +12,7 @@ import { validateIdentifier, validateText, validateAgentSpawn } from './validate
|
|
|
12
12
|
import { executeAgentTask } from './agent-execute-core.js';
|
|
13
13
|
import { AGENT_HEALTH_WINDOW, summarizeAgentExecutions } from './agent-health-metrics.js';
|
|
14
14
|
import { pheromoneAgentEligibility } from './swarm-tools.js';
|
|
15
|
+
import { noteFirstAgentRun } from '../funnel/activation.js';
|
|
15
16
|
// Storage paths
|
|
16
17
|
const STORAGE_DIR = '.claude-flow';
|
|
17
18
|
const AGENT_DIR = 'agents';
|
|
@@ -425,6 +426,7 @@ export const agentTools = [
|
|
|
425
426
|
catch { /* COW branch is best-effort; agent already registered */ }
|
|
426
427
|
}
|
|
427
428
|
// Include deterministic codemod routing info if applicable
|
|
429
|
+
noteFirstAgentRun('cli'); // activation funnel (opt-in; no-op without telemetry consent)
|
|
428
430
|
const response = {
|
|
429
431
|
success: true,
|
|
430
432
|
agentId,
|
|
@@ -434,6 +434,10 @@ export const memoryTools = [
|
|
|
434
434
|
items: { type: 'string' },
|
|
435
435
|
description: 'Optional tags for filtering',
|
|
436
436
|
},
|
|
437
|
+
metadata: {
|
|
438
|
+
type: 'object',
|
|
439
|
+
description: 'Optional JSON object stored with the entry (e.g. { confidence: 0.9 }, which AutoMemoryBridge reads for sync eligibility). Replaces any existing metadata on upsert.',
|
|
440
|
+
},
|
|
437
441
|
ttl: { type: 'number', description: 'Time-to-live in seconds (optional)' },
|
|
438
442
|
upsert: { type: 'boolean', description: 'Update existing key instead of failing (default: true, matching CLI `memory store`; set false for strict-insert). #2775 parity.' },
|
|
439
443
|
provenance_type: {
|
|
@@ -465,6 +469,18 @@ export const memoryTools = [
|
|
|
465
469
|
// ADR-323: leave undefined when omitted so storeEntry's own default
|
|
466
470
|
// ('unknown') applies uniformly across the CLI, MCP tool, and other callers.
|
|
467
471
|
const provenanceType = input.provenance_type;
|
|
472
|
+
// #1426: metadata must be a plain JSON object; reject arrays, null and scalars.
|
|
473
|
+
const rawMetadata = input.metadata;
|
|
474
|
+
if (rawMetadata !== undefined && (typeof rawMetadata !== 'object' || rawMetadata === null || Array.isArray(rawMetadata))) {
|
|
475
|
+
return {
|
|
476
|
+
success: false,
|
|
477
|
+
key,
|
|
478
|
+
stored: false,
|
|
479
|
+
hasEmbedding: false,
|
|
480
|
+
error: 'metadata must be a JSON object',
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
const metadata = rawMetadata;
|
|
468
484
|
if (!value) {
|
|
469
485
|
return {
|
|
470
486
|
success: false,
|
|
@@ -488,6 +504,7 @@ export const memoryTools = [
|
|
|
488
504
|
namespace,
|
|
489
505
|
generateEmbeddingFlag: true,
|
|
490
506
|
tags,
|
|
507
|
+
metadata,
|
|
491
508
|
ttl,
|
|
492
509
|
upsert,
|
|
493
510
|
provenanceType,
|
|
@@ -54,6 +54,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
54
54
|
import { homedir } from 'node:os';
|
|
55
55
|
import { ownPluginDirs } from '../plugins/own-install.js';
|
|
56
56
|
import { runFlywheelWorker } from '../services/harness-flywheel-runtime.js';
|
|
57
|
+
import { benchVerify, gepaOp } from '../metaharness/darwin-lib.js';
|
|
57
58
|
import { evaluatePolicyRequest } from '../services/policy-runtime.js';
|
|
58
59
|
import { listFlywheelReceipts, promoteFlywheelCandidate, readFlywheelTransactionState, resetSequentialEvidence, verifyFlywheelLedger, } from '../services/flywheel-transaction.js';
|
|
59
60
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -523,6 +524,13 @@ export const metaharnessTools = [
|
|
|
523
524
|
required: ['op'],
|
|
524
525
|
},
|
|
525
526
|
handler: async (input) => {
|
|
527
|
+
// ADR-526: verify runs in-process (darwin is a regular dependency);
|
|
528
|
+
// create still goes through the darwin CLI, which owns the scaffold task.
|
|
529
|
+
if (input.op === 'verify') {
|
|
530
|
+
const inProc = await benchVerify(String(input.suite ?? ''));
|
|
531
|
+
if (inProc)
|
|
532
|
+
return { success: inProc.exitCode === 0, data: inProc.json, degraded: false, exitCode: inProc.exitCode };
|
|
533
|
+
}
|
|
526
534
|
const args = ['--op', String(input.op)];
|
|
527
535
|
if (input.repo)
|
|
528
536
|
args.push('--repo', String(input.repo));
|
|
@@ -665,6 +673,17 @@ export const metaharnessTools = [
|
|
|
665
673
|
required: ['op'],
|
|
666
674
|
},
|
|
667
675
|
handler: async (input) => {
|
|
676
|
+
// ADR-526: GEPA genome ops run in-process; the plugin script is the fallback.
|
|
677
|
+
const inProc = await gepaOp({
|
|
678
|
+
op: String(input.op),
|
|
679
|
+
path: input.path ? String(input.path) : undefined,
|
|
680
|
+
transcript: input.transcript ? String(input.transcript) : undefined,
|
|
681
|
+
ext: input.ext ? String(input.ext) : undefined,
|
|
682
|
+
glob: input.glob ? String(input.glob) : undefined,
|
|
683
|
+
alertOnInvalid: input.alertOnInvalid === true,
|
|
684
|
+
});
|
|
685
|
+
if (inProc)
|
|
686
|
+
return { success: inProc.exitCode === 0, data: inProc.json, degraded: false, exitCode: inProc.exitCode };
|
|
668
687
|
const args = ['--op', String(input.op)];
|
|
669
688
|
if (input.path)
|
|
670
689
|
args.push('--path', String(input.path));
|
|
@@ -9,6 +9,7 @@ import { join } from 'node:path';
|
|
|
9
9
|
import { nullPrototypeRecords, readPreservedJsonStore } from './record-store.js';
|
|
10
10
|
import { getProjectCwd } from './types.js';
|
|
11
11
|
import { validateIdentifier } from './validate-input.js';
|
|
12
|
+
import { noteFirstAgentRun } from '../funnel/activation.js';
|
|
12
13
|
import { createApscState, isApscAgentEligible, normalizeApscConfig, recordApscSignal, } from '../services/pheromone-adaptive.js';
|
|
13
14
|
// Swarm state persistence
|
|
14
15
|
const SWARM_DIR = '.claude-flow/swarm';
|
|
@@ -283,6 +284,7 @@ export const swarmTools = [
|
|
|
283
284
|
const store = loadSwarmStore();
|
|
284
285
|
store.swarms[swarmId] = swarmState;
|
|
285
286
|
saveSwarmStore(store);
|
|
287
|
+
noteFirstAgentRun('cli'); // activation funnel (opt-in; no-op without telemetry consent)
|
|
286
288
|
return {
|
|
287
289
|
success: true,
|
|
288
290
|
swarmId,
|
|
@@ -74,6 +74,8 @@ export declare function bridgeStoreEntry(options: {
|
|
|
74
74
|
namespace?: string;
|
|
75
75
|
generateEmbeddingFlag?: boolean;
|
|
76
76
|
tags?: string[];
|
|
77
|
+
/** #1426: JSON object persisted to the metadata column. */
|
|
78
|
+
metadata?: Record<string, unknown>;
|
|
77
79
|
ttl?: number;
|
|
78
80
|
dbPath?: string;
|
|
79
81
|
upsert?: boolean;
|
|
@@ -1182,7 +1182,7 @@ export async function bridgeStoreEntry(options) {
|
|
|
1182
1182
|
}
|
|
1183
1183
|
catch { /* vector_indexes may not exist on legacy DBs — fall through */ }
|
|
1184
1184
|
const stmt = ctx.db.prepare(insertSql);
|
|
1185
|
-
const insert = () => stmt.run(id, key, namespace, value, embeddingJson, dimensions || null, model, tags.length > 0 ? JSON.stringify(tags) : null,
|
|
1185
|
+
const insert = () => stmt.run(id, key, namespace, value, embeddingJson, dimensions || null, model, tags.length > 0 ? JSON.stringify(tags) : null, JSON.stringify(options.metadata ?? {}), provenanceType, now, now, ttl ? now + (ttl * 1000) : null);
|
|
1186
1186
|
let runResult;
|
|
1187
1187
|
try {
|
|
1188
1188
|
runResult = options.appendOnly
|
|
@@ -444,6 +444,8 @@ export declare function storeEntry(options: {
|
|
|
444
444
|
namespace?: string;
|
|
445
445
|
generateEmbeddingFlag?: boolean;
|
|
446
446
|
tags?: string[];
|
|
447
|
+
/** #1426: JSON object persisted to the metadata column. */
|
|
448
|
+
metadata?: Record<string, unknown>;
|
|
447
449
|
ttl?: number;
|
|
448
450
|
dbPath?: string;
|
|
449
451
|
upsert?: boolean;
|
|
@@ -2733,7 +2733,7 @@ export async function storeEntry(options) {
|
|
|
2733
2733
|
embeddingDimensions,
|
|
2734
2734
|
embeddingModel,
|
|
2735
2735
|
tags.length > 0 ? JSON.stringify(tags) : null,
|
|
2736
|
-
|
|
2736
|
+
JSON.stringify(options.metadata ?? {}),
|
|
2737
2737
|
persistedProvenance,
|
|
2738
2738
|
now,
|
|
2739
2739
|
now,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-process access to @metaharness/darwin (ADR-526).
|
|
3
|
+
*
|
|
4
|
+
* darwin is a regular dependency of the CLI, so the long-lived MCP server can
|
|
5
|
+
* import it once and answer `metaharness_bench --op verify` and every
|
|
6
|
+
* `metaharness_gepa` op without spawning `node bench.mjs` → `node darwin/cli.js`
|
|
7
|
+
* per call. Each function returns the same {exitCode, json} pair the plugin
|
|
8
|
+
* script would have produced, so callers keep the existing
|
|
9
|
+
* {success, data, degraded, exitCode} contract.
|
|
10
|
+
*
|
|
11
|
+
* Graceful degradation (ADR-150 rule 3) still holds: if the import fails, every
|
|
12
|
+
* function returns null and the caller falls back to the subprocess path.
|
|
13
|
+
*/
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
export interface DarwinResult {
|
|
16
|
+
exitCode: number;
|
|
17
|
+
/** Parsed payload, or null where the script would have printed only to stderr. */
|
|
18
|
+
json: unknown;
|
|
19
|
+
}
|
|
20
|
+
type DarwinRoot = {
|
|
21
|
+
bench: {
|
|
22
|
+
loadSuite(path: string): Promise<{
|
|
23
|
+
id: string;
|
|
24
|
+
version: string;
|
|
25
|
+
tasks: unknown[];
|
|
26
|
+
}>;
|
|
27
|
+
verifySuite(suite: unknown): {
|
|
28
|
+
ok: boolean;
|
|
29
|
+
actual: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
type DarwinGepa = {
|
|
34
|
+
loadGenome(read: typeof readFileSync, path: string): unknown;
|
|
35
|
+
loadCand6Genome(): unknown;
|
|
36
|
+
validateGenome(genome: unknown): unknown[];
|
|
37
|
+
buildSystemFromGenome(genome: unknown, ext?: string, glob?: string): string;
|
|
38
|
+
analyzeTranscript(entries: unknown[]): unknown;
|
|
39
|
+
CAND6_GENOME_PATH: string;
|
|
40
|
+
};
|
|
41
|
+
export declare function loadDarwin(): Promise<DarwinRoot | null>;
|
|
42
|
+
export declare function loadDarwinGepa(): Promise<DarwinGepa | null>;
|
|
43
|
+
/** Test hook: forget the cached modules. */
|
|
44
|
+
export declare function resetDarwinCache(): void;
|
|
45
|
+
/**
|
|
46
|
+
* `bench verify <suite>` in-process. Mirrors plugins/ruflo-metaharness/scripts/bench.mjs:
|
|
47
|
+
* exit 2 + no payload for a missing path, exit 1 when the suite fails to load
|
|
48
|
+
* (darwin's loadSuite throws on tamper) or its hash does not match, exit 0 otherwise.
|
|
49
|
+
* Returns null when darwin cannot be imported.
|
|
50
|
+
*/
|
|
51
|
+
export declare function benchVerify(suitePath: string): Promise<DarwinResult | null>;
|
|
52
|
+
export interface GepaInput {
|
|
53
|
+
op: string;
|
|
54
|
+
path?: string;
|
|
55
|
+
transcript?: string;
|
|
56
|
+
ext?: string;
|
|
57
|
+
glob?: string;
|
|
58
|
+
alertOnInvalid?: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* GEPA genome ops in-process. Mirrors plugins/ruflo-metaharness/scripts/gepa.mjs,
|
|
62
|
+
* including its exit codes: 2 for input errors (no payload), 1 for an invalid
|
|
63
|
+
* genome with alertOnInvalid, 0 otherwise. Returns null when darwin/gepa cannot
|
|
64
|
+
* be imported.
|
|
65
|
+
*/
|
|
66
|
+
export declare function gepaOp(input: GepaInput): Promise<DarwinResult | null>;
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=darwin-lib.d.ts.map
|