@claude-flow/cli 3.25.6 → 3.26.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/.proven-config-version +1 -0
- package/.claude/helpers/.helpers-version +1 -1
- package/.claude/helpers/helpers.manifest.json +5 -4
- package/.claude/helpers/hook-handler.cjs +129 -0
- package/.claude/helpers/statusline.cjs +771 -505
- package/.claude/proven-config.json +42 -0
- package/catalog-manifest.json +12 -0
- package/dist/src/commands/advisor.d.ts +15 -0
- package/dist/src/commands/advisor.js +94 -0
- package/dist/src/commands/doctor.js +77 -3
- package/dist/src/commands/funnel.d.ts +13 -0
- package/dist/src/commands/funnel.js +103 -0
- package/dist/src/commands/hooks.js +112 -46
- package/dist/src/commands/index.js +10 -0
- package/dist/src/commands/init.js +44 -0
- package/dist/src/commands/proxy.d.ts +21 -0
- package/dist/src/commands/proxy.js +310 -0
- package/dist/src/commands/security.js +29 -0
- package/dist/src/commands/settings.d.ts +19 -0
- package/dist/src/commands/settings.js +180 -0
- package/dist/src/commands/version.d.ts +42 -0
- package/dist/src/commands/version.js +106 -0
- package/dist/src/funnel/advisor-tip.d.ts +58 -0
- package/dist/src/funnel/advisor-tip.js +92 -0
- package/dist/src/funnel/attribution.d.ts +37 -0
- package/dist/src/funnel/attribution.js +101 -0
- package/dist/src/funnel/consent.d.ts +22 -0
- package/dist/src/funnel/consent.js +55 -0
- package/dist/src/funnel/credit-errors.d.ts +31 -0
- package/dist/src/funnel/credit-errors.js +88 -0
- package/dist/src/funnel/credit-notifier.d.ts +44 -0
- package/dist/src/funnel/credit-notifier.js +74 -0
- package/dist/src/funnel/disclosure.d.ts +46 -0
- package/dist/src/funnel/disclosure.js +97 -0
- package/dist/src/funnel/enrollment.d.ts +36 -0
- package/dist/src/funnel/enrollment.js +64 -0
- package/dist/src/funnel/environment.d.ts +17 -0
- package/dist/src/funnel/environment.js +39 -0
- package/dist/src/funnel/event-transport.d.ts +51 -0
- package/dist/src/funnel/event-transport.js +199 -0
- package/dist/src/funnel/events.d.ts +42 -0
- package/dist/src/funnel/events.js +150 -0
- package/dist/src/funnel/index.d.ts +20 -0
- package/dist/src/funnel/index.js +20 -0
- package/dist/src/funnel/insights.d.ts +49 -0
- package/dist/src/funnel/insights.js +120 -0
- package/dist/src/funnel/local-signals.d.ts +15 -0
- package/dist/src/funnel/local-signals.js +68 -0
- package/dist/src/funnel/message-transport.d.ts +51 -0
- package/dist/src/funnel/message-transport.js +149 -0
- package/dist/src/funnel/messages.d.ts +55 -0
- package/dist/src/funnel/messages.js +160 -0
- package/dist/src/funnel/power-saver-notifier.d.ts +44 -0
- package/dist/src/funnel/power-saver-notifier.js +92 -0
- package/dist/src/funnel/precedence.d.ts +16 -0
- package/dist/src/funnel/precedence.js +85 -0
- package/dist/src/funnel/promo.d.ts +41 -0
- package/dist/src/funnel/promo.js +144 -0
- package/dist/src/funnel/rate-limit-notifier.d.ts +55 -0
- package/dist/src/funnel/rate-limit-notifier.js +102 -0
- package/dist/src/funnel/rotation.d.ts +19 -0
- package/dist/src/funnel/rotation.js +70 -0
- package/dist/src/funnel/state.d.ts +13 -0
- package/dist/src/funnel/state.js +52 -0
- package/dist/src/funnel/toggle-cooldown.d.ts +17 -0
- package/dist/src/funnel/toggle-cooldown.js +32 -0
- package/dist/src/funnel/types.d.ts +86 -0
- package/dist/src/funnel/types.js +26 -0
- package/dist/src/init/executor.js +9 -3
- package/dist/src/init/helper-refresh.d.ts +33 -2
- package/dist/src/init/helper-refresh.js +102 -12
- package/dist/src/init/helpers-generator.js +52 -0
- package/dist/src/init/statusline-generator.js +282 -116
- package/dist/src/services/daemon-autostart.js +28 -5
- package/dist/src/services/fable-harness.d.ts +39 -0
- package/dist/src/services/fable-harness.js +41 -0
- package/package.json +13 -4
- package/dist/src/ruvector/lattice-wasm.d.ts +0 -14
- package/dist/src/ruvector/lattice-wasm.js +0 -144
- package/plugins/ruflo-metaharness/.claude-flow/daemon-state.json +0 -178
- package/plugins/ruflo-metaharness/.claude-flow/daemon.pid +0 -1
- package/plugins/ruflo-metaharness/.claude-flow/data/pending-insights.jsonl +0 -5
- package/plugins/ruflo-metaharness/.claude-flow/logs/daemon.log +0 -43
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/audit_1783604774864_ozbujc_prompt.log +0 -19
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/audit_1783604774864_ozbujc_result.log +0 -108
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/audit_1783605513587_ulvmpb_prompt.log +0 -19
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/audit_1783605513587_ulvmpb_result.log +0 -209
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/audit_1783606368867_ahysui_prompt.log +0 -19
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/audit_1783606368867_ahysui_result.log +0 -192
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/optimize_1783604894861_v6n3ut_prompt.log +0 -14
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/optimize_1783604894861_v6n3ut_result.log +0 -66
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/optimize_1783605934532_9h8ikb_prompt.log +0 -14
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/optimize_1783605934532_9h8ikb_result.log +0 -68
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/testgaps_1783605134860_9jssz9_prompt.log +0 -14
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/testgaps_1783605134860_9jssz9_result.log +0 -69
- package/plugins/ruflo-metaharness/.claude-flow/logs/headless/testgaps_1783606516743_zftbaa_prompt.log +0 -14
- package/plugins/ruflo-metaharness/.claude-flow/metrics/backup.json +0 -7
- package/plugins/ruflo-metaharness/.claude-flow/metrics/codebase-map.json +0 -11
- package/plugins/ruflo-metaharness/.claude-flow/metrics/consolidation.json +0 -16
- package/plugins/ruflo-metaharness/.claude-flow/metrics/harness-loop.json +0 -83
- package/plugins/ruflo-metaharness/.claude-flow/metrics/performance.json +0 -67
- package/plugins/ruflo-metaharness/.claude-flow/metrics/security-audit.json +0 -191
- package/plugins/ruflo-metaharness/.claude-flow/metrics/test-gaps.json +0 -68
- package/plugins/ruflo-metaharness/.claude-flow/neural/stats.json +0 -6
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adoptedAt": 1783692950735,
|
|
3
|
+
"championId": "sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319",
|
|
4
|
+
"manifest": {
|
|
5
|
+
"schema": "ruflo.proven-config/v1",
|
|
6
|
+
"policy": {
|
|
7
|
+
"ref": "sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319",
|
|
8
|
+
"value": {
|
|
9
|
+
"alpha": 0.3,
|
|
10
|
+
"subjectWeight": 1,
|
|
11
|
+
"mmrLambda": 0.5,
|
|
12
|
+
"bodyWeight": 1.5,
|
|
13
|
+
"typePenaltyFactor": 0.5
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"layer": "framework/node-cli",
|
|
17
|
+
"compatibility": {
|
|
18
|
+
"ruflo": ">=3.24.0"
|
|
19
|
+
},
|
|
20
|
+
"benchmark": {
|
|
21
|
+
"corpus": "ADR-081-labelled-v1",
|
|
22
|
+
"corpusHash": "sha256:2f700b5c363e20a3bd88ce2bc9b87bbbbaa61732c6177894c6ec37890f888982"
|
|
23
|
+
},
|
|
24
|
+
"receipt": {
|
|
25
|
+
"heldOutDelta": 0.07381404928570845,
|
|
26
|
+
"redblue": "PASS",
|
|
27
|
+
"drift": 0,
|
|
28
|
+
"canary": {
|
|
29
|
+
"rollbackRate": 0,
|
|
30
|
+
"latencyP95": 244.612458000076,
|
|
31
|
+
"costPerTask": 0
|
|
32
|
+
},
|
|
33
|
+
"receiptCoverage": 1
|
|
34
|
+
},
|
|
35
|
+
"platform": [
|
|
36
|
+
"linux",
|
|
37
|
+
"macOS",
|
|
38
|
+
"windows"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"previous": ""
|
|
42
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ruflo advisor` — Fable co-pilot advisor tip in the statusline insight
|
|
3
|
+
* ticker (ADR-316).
|
|
4
|
+
*
|
|
5
|
+
* Client-side only — unlike `proxy.ts`'s sponsored-downtime/power-saver/
|
|
6
|
+
* training-share domains, this feature has NO proxy-config.toml mirror,
|
|
7
|
+
* because it never talks to the meta-proxy Rust binary. It calls
|
|
8
|
+
* `claude -p` directly via services/fable-harness.ts (ADR-172's existing
|
|
9
|
+
* cost-disciplined harness) and caches the result for the insight ticker
|
|
10
|
+
* (funnel/insights.ts) to read synchronously.
|
|
11
|
+
*/
|
|
12
|
+
import type { Command } from '../types.js';
|
|
13
|
+
export declare const advisorCommand: Command;
|
|
14
|
+
export default advisorCommand;
|
|
15
|
+
//# sourceMappingURL=advisor.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ruflo advisor` — Fable co-pilot advisor tip in the statusline insight
|
|
3
|
+
* ticker (ADR-316).
|
|
4
|
+
*
|
|
5
|
+
* Client-side only — unlike `proxy.ts`'s sponsored-downtime/power-saver/
|
|
6
|
+
* training-share domains, this feature has NO proxy-config.toml mirror,
|
|
7
|
+
* because it never talks to the meta-proxy Rust binary. It calls
|
|
8
|
+
* `claude -p` directly via services/fable-harness.ts (ADR-172's existing
|
|
9
|
+
* cost-disciplined harness) and caches the result for the insight ticker
|
|
10
|
+
* (funnel/insights.ts) to read synchronously.
|
|
11
|
+
*/
|
|
12
|
+
import { output } from '../output.js';
|
|
13
|
+
import { hasConsent, recordConsent, revokeConsent } from '../funnel/consent.js';
|
|
14
|
+
import { recordFunnelEvent } from '../funnel/events.js';
|
|
15
|
+
import { getInstalledCliVersion } from '../init/helper-refresh.js';
|
|
16
|
+
import { readAdvisorTip, ADVISOR_DEFAULT_BUDGET_USD } from '../funnel/advisor-tip.js';
|
|
17
|
+
const ADVISOR_DISCLOSURE = [
|
|
18
|
+
'Enabling the co-pilot advisor tip.',
|
|
19
|
+
'',
|
|
20
|
+
'At most once per day, ruflo will send a small STRUCTURAL snapshot of your',
|
|
21
|
+
'session — security scan status, swarm/agent state, git uncommitted-file',
|
|
22
|
+
'COUNT — never raw prompts, file contents, or commands — to a headless',
|
|
23
|
+
'Fable model (via `claude -p`) and cache one short, actionable tip for the',
|
|
24
|
+
'statusline insight ticker.',
|
|
25
|
+
'',
|
|
26
|
+
`This is a real, metered API call (~$${ADVISOR_DEFAULT_BUDGET_USD.toFixed(2)} budget cap per`,
|
|
27
|
+
'refresh, at most once/day). Disable anytime: ruflo advisor disable',
|
|
28
|
+
].join('\n');
|
|
29
|
+
const enableSub = {
|
|
30
|
+
name: 'enable',
|
|
31
|
+
description: 'Opt into the co-pilot advisor tip (ADR-316)',
|
|
32
|
+
options: [
|
|
33
|
+
{ name: 'yes', description: 'Skip the confirmation prompt', type: 'boolean', default: false },
|
|
34
|
+
],
|
|
35
|
+
action: async (ctx) => {
|
|
36
|
+
if (hasConsent('advisor-tips')) {
|
|
37
|
+
output.writeln('Advisor tip is already enabled.');
|
|
38
|
+
return { success: true, data: { alreadyEnabled: true } };
|
|
39
|
+
}
|
|
40
|
+
output.writeln(ADVISOR_DISCLOSURE);
|
|
41
|
+
output.writeln('');
|
|
42
|
+
if (!ctx.flags.yes) {
|
|
43
|
+
output.writeln('Re-run with --yes to confirm: ruflo advisor enable --yes');
|
|
44
|
+
return { success: true, data: { confirmed: false } };
|
|
45
|
+
}
|
|
46
|
+
recordConsent('advisor-tips', true, 'advisor-enable');
|
|
47
|
+
recordFunnelEvent('advisor_tip_enabled', 'statusline', getInstalledCliVersion());
|
|
48
|
+
output.printSuccess('Advisor tip enabled.');
|
|
49
|
+
output.writeln('It refreshes at most once/day, in the background, on session-restore.');
|
|
50
|
+
output.writeln('Disable anytime: ruflo advisor disable');
|
|
51
|
+
return { success: true, data: { confirmed: true } };
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
const disableSub = {
|
|
55
|
+
name: 'disable',
|
|
56
|
+
description: 'Revoke advisor-tip consent and stop generating new tips',
|
|
57
|
+
action: async () => {
|
|
58
|
+
revokeConsent('advisor-tips', 'advisor-disable');
|
|
59
|
+
recordFunnelEvent('advisor_tip_disabled', 'statusline', getInstalledCliVersion());
|
|
60
|
+
output.printSuccess('Advisor tip disabled.');
|
|
61
|
+
return { success: true };
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const statusSub = {
|
|
65
|
+
name: 'status',
|
|
66
|
+
description: 'Show advisor-tip consent state and the current cached tip',
|
|
67
|
+
action: async () => {
|
|
68
|
+
const consented = hasConsent('advisor-tips');
|
|
69
|
+
output.writeln(`Advisor tip consent: ${consented ? 'granted' : 'not granted'}`);
|
|
70
|
+
const tip = consented ? readAdvisorTip() : null;
|
|
71
|
+
if (tip) {
|
|
72
|
+
output.writeln(`Current tip: ${tip.headline}`);
|
|
73
|
+
if (tip.detail)
|
|
74
|
+
output.writeln(` ${tip.detail}`);
|
|
75
|
+
}
|
|
76
|
+
else if (consented) {
|
|
77
|
+
output.writeln('No cached tip yet (refreshes at most once/day on session-restore).');
|
|
78
|
+
}
|
|
79
|
+
return { success: true, data: { consented, tip } };
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
export const advisorCommand = {
|
|
83
|
+
name: 'advisor',
|
|
84
|
+
description: 'Fable co-pilot advisor tip in the statusline insight ticker (ADR-316)',
|
|
85
|
+
subcommands: [enableSub, disableSub, statusSub],
|
|
86
|
+
examples: [
|
|
87
|
+
{ command: 'ruflo advisor enable --yes', description: 'Opt into the co-pilot advisor tip' },
|
|
88
|
+
{ command: 'ruflo advisor status', description: 'Show consent state + current cached tip' },
|
|
89
|
+
{ command: 'ruflo advisor disable', description: 'Revoke consent and stop generating tips' },
|
|
90
|
+
],
|
|
91
|
+
action: statusSub.action,
|
|
92
|
+
};
|
|
93
|
+
export default advisorCommand;
|
|
94
|
+
//# sourceMappingURL=advisor.js.map
|
|
@@ -135,8 +135,10 @@ async function checkConfigFile() {
|
|
|
135
135
|
* the broken commands).
|
|
136
136
|
*/
|
|
137
137
|
async function checkStaleSettingsNpx() {
|
|
138
|
-
// Same regex pattern the executor migration uses — kept in sync.
|
|
139
|
-
|
|
138
|
+
// Same regex pattern the executor migration uses — kept in sync. Flag-list
|
|
139
|
+
// repetition bounded at 10 (CodeQL js/redos — unbounded `*` here is
|
|
140
|
+
// exponential-backtracking-prone on a crafted settings.json).
|
|
141
|
+
const BROKEN_RE = /npx\s+(?:--?\S+\s+){0,10}@?claude-flow\/cli@latest\s+hooks\s+(?:statusline|\S+)/;
|
|
140
142
|
// Look in both project-local and home-dir settings.
|
|
141
143
|
const candidates = [
|
|
142
144
|
join(process.cwd(), '.claude', 'settings.json'),
|
|
@@ -711,6 +713,74 @@ async function checkVersionFreshness() {
|
|
|
711
713
|
* - plugins/ruflo-metaharness/skills/harness-similarity/SKILL.md
|
|
712
714
|
* - plugins/ruflo-metaharness/skills/harness-drift-from-history/SKILL.md (iter 53)
|
|
713
715
|
*/
|
|
716
|
+
/**
|
|
717
|
+
* ADR-305 — funnel state audit. Reports the effective enabled/disabled
|
|
718
|
+
* state and, critically for enterprise audit verification, WHICH
|
|
719
|
+
* precedence source decided it (env > enterprise > user > default).
|
|
720
|
+
* Informational: both states are `pass`; only a resolver failure warns.
|
|
721
|
+
*/
|
|
722
|
+
async function checkFunnel() {
|
|
723
|
+
try {
|
|
724
|
+
const { resolveFunnelEnabled, getDisclosure } = await import('../funnel/index.js');
|
|
725
|
+
const decision = resolveFunnelEnabled();
|
|
726
|
+
const disclosure = getDisclosure();
|
|
727
|
+
return {
|
|
728
|
+
name: 'Funnel (ADR-305)',
|
|
729
|
+
status: 'pass',
|
|
730
|
+
message: `${decision.enabled ? 'enabled' : 'disabled'} (decided by: ${decision.decidedBy}; disclosure: ${disclosure.state})`,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
catch (err) {
|
|
734
|
+
return {
|
|
735
|
+
name: 'Funnel (ADR-305)',
|
|
736
|
+
status: 'warn',
|
|
737
|
+
message: `state unreadable: ${err instanceof Error ? err.message : String(err)}`,
|
|
738
|
+
fix: 'ruflo funnel status',
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
/** Meta LLM Proxy — sponsored-downtime health (ADR-313). */
|
|
743
|
+
async function checkProxy() {
|
|
744
|
+
try {
|
|
745
|
+
const { funnelStateDir, hasConsent, readRateLimitStatus, lastRecordedEvent } = await import('../funnel/index.js');
|
|
746
|
+
const dir = funnelStateDir();
|
|
747
|
+
const installed = existsSync(join(dir, 'proxy-token'));
|
|
748
|
+
const consented = hasConsent('sponsored-downtime');
|
|
749
|
+
const rateLimited = readRateLimitStatus();
|
|
750
|
+
const lastExhausted = lastRecordedEvent('sponsor_capacity_exhausted');
|
|
751
|
+
if (!installed) {
|
|
752
|
+
return {
|
|
753
|
+
name: 'Meta LLM Proxy (ADR-313)',
|
|
754
|
+
status: 'warn',
|
|
755
|
+
message: 'not installed — no proxy-token found; sponsored-downtime capacity is unavailable',
|
|
756
|
+
fix: 'See cognitum-one/meta-proxy (private) for install instructions',
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
const parts = [
|
|
760
|
+
`sponsored consent: ${consented ? 'granted' : 'not granted'}`,
|
|
761
|
+
`rate-limit flag: ${rateLimited.limited ? `set (since ${rateLimited.since})` : 'not set'}`,
|
|
762
|
+
];
|
|
763
|
+
if (lastExhausted)
|
|
764
|
+
parts.push(`last capacity-exhausted: ${lastExhausted}`);
|
|
765
|
+
if (rateLimited.limited && !consented) {
|
|
766
|
+
return {
|
|
767
|
+
name: 'Meta LLM Proxy (ADR-313)',
|
|
768
|
+
status: 'warn',
|
|
769
|
+
message: `${parts.join('; ')} — flagged rate-limited but sponsored capacity isn't enabled`,
|
|
770
|
+
fix: 'ruflo proxy sponsor-enable --yes',
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
return { name: 'Meta LLM Proxy (ADR-313)', status: 'pass', message: parts.join('; ') };
|
|
774
|
+
}
|
|
775
|
+
catch (err) {
|
|
776
|
+
return {
|
|
777
|
+
name: 'Meta LLM Proxy (ADR-313)',
|
|
778
|
+
status: 'warn',
|
|
779
|
+
message: `state unreadable: ${err instanceof Error ? err.message : String(err)}`,
|
|
780
|
+
fix: 'ruflo proxy sponsor-status',
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
}
|
|
714
784
|
async function checkMetaharnessIntegration() {
|
|
715
785
|
// Locate plugins dir.
|
|
716
786
|
//
|
|
@@ -1087,7 +1157,7 @@ export const doctorCommand = {
|
|
|
1087
1157
|
{
|
|
1088
1158
|
name: 'component',
|
|
1089
1159
|
short: 'c',
|
|
1090
|
-
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, claude, disk, typescript, agentic-flow, encryption, federation, metaharness)',
|
|
1160
|
+
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, claude, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, metaharness)',
|
|
1091
1161
|
type: 'string'
|
|
1092
1162
|
},
|
|
1093
1163
|
{
|
|
@@ -1137,6 +1207,8 @@ export const doctorCommand = {
|
|
|
1137
1207
|
checkFederationBreaker, // ADR-097 Phase 4
|
|
1138
1208
|
checkMetaharness, // ADR-150 — MetaHarness upstream package
|
|
1139
1209
|
checkMetaharnessIntegration, // iter 45 — ruflo-side integration layer
|
|
1210
|
+
checkFunnel, // ADR-305 — effective funnel state + deciding precedence source
|
|
1211
|
+
checkProxy, // ADR-313 — Meta LLM Proxy sponsored-downtime health
|
|
1140
1212
|
];
|
|
1141
1213
|
const componentMap = {
|
|
1142
1214
|
'version': checkVersionFreshness,
|
|
@@ -1161,6 +1233,8 @@ export const doctorCommand = {
|
|
|
1161
1233
|
'federation': checkFederationBreaker, // ADR-097 Phase 4
|
|
1162
1234
|
'metaharness': checkMetaharness, // ADR-150 — upstream package
|
|
1163
1235
|
'metaharness-integration': checkMetaharnessIntegration, // iter 45 — ruflo-side
|
|
1236
|
+
'funnel': checkFunnel, // ADR-305
|
|
1237
|
+
'proxy': checkProxy, // ADR-313
|
|
1164
1238
|
};
|
|
1165
1239
|
let checksToRun = allChecks;
|
|
1166
1240
|
if (component && componentMap[component]) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ruflo funnel` — user control surface for the Cognitum lifecycle funnel
|
|
3
|
+
* (ADR-301/305/309).
|
|
4
|
+
*
|
|
5
|
+
* funnel status effective state and which precedence source decided it
|
|
6
|
+
* funnel disable user-level opt-out (all surfaces) + delete funnel data
|
|
7
|
+
* funnel enable re-enable at the user tier (cannot override env/enterprise)
|
|
8
|
+
* funnel id print the pseudonymous funnel ID, if one exists
|
|
9
|
+
*/
|
|
10
|
+
import type { Command } from '../types.js';
|
|
11
|
+
export declare const funnelCommand: Command;
|
|
12
|
+
export default funnelCommand;
|
|
13
|
+
//# sourceMappingURL=funnel.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ruflo funnel` — user control surface for the Cognitum lifecycle funnel
|
|
3
|
+
* (ADR-301/305/309).
|
|
4
|
+
*
|
|
5
|
+
* funnel status effective state and which precedence source decided it
|
|
6
|
+
* funnel disable user-level opt-out (all surfaces) + delete funnel data
|
|
7
|
+
* funnel enable re-enable at the user tier (cannot override env/enterprise)
|
|
8
|
+
* funnel id print the pseudonymous funnel ID, if one exists
|
|
9
|
+
*/
|
|
10
|
+
import { output } from '../output.js';
|
|
11
|
+
import { deleteFunnelData, funnelStateDir, getDisclosure, getFunnelId, readConsents, recordDisclosureDeclined, recordDisclosureReenabled, resolveFunnelEnabled, } from '../funnel/index.js';
|
|
12
|
+
import { readStateJson, writeStateJson } from '../funnel/state.js';
|
|
13
|
+
function setUserConfigEnabled(enabled) {
|
|
14
|
+
const cfg = readStateJson('funnel.json') ?? {};
|
|
15
|
+
cfg.enabled = enabled;
|
|
16
|
+
writeStateJson('funnel.json', cfg);
|
|
17
|
+
}
|
|
18
|
+
const statusSub = {
|
|
19
|
+
name: 'status',
|
|
20
|
+
description: 'Show effective funnel state and which source decided it',
|
|
21
|
+
options: [
|
|
22
|
+
{ name: 'json', description: 'Output as JSON', type: 'boolean', default: false },
|
|
23
|
+
],
|
|
24
|
+
action: async (ctx) => {
|
|
25
|
+
const decision = resolveFunnelEnabled();
|
|
26
|
+
const disclosure = getDisclosure();
|
|
27
|
+
const consents = readConsents();
|
|
28
|
+
const data = {
|
|
29
|
+
enabled: decision.enabled,
|
|
30
|
+
decidedBy: decision.decidedBy,
|
|
31
|
+
disclosure: disclosure.state,
|
|
32
|
+
stateDir: funnelStateDir(),
|
|
33
|
+
consents,
|
|
34
|
+
};
|
|
35
|
+
if (ctx.flags.json) {
|
|
36
|
+
output.printJson(data);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
output.writeln(`Funnel: ${decision.enabled ? 'enabled' : 'disabled'} (decided by: ${decision.decidedBy})`);
|
|
40
|
+
output.writeln(`Disclosure: ${disclosure.state}`);
|
|
41
|
+
output.writeln(`State dir: ${funnelStateDir()}`);
|
|
42
|
+
const domains = Object.keys(consents);
|
|
43
|
+
output.writeln(domains.length
|
|
44
|
+
? `Consents: ${domains.map((d) => `${d}=${consents[d]?.granted ? 'granted' : 'declined'}`).join(', ')}`
|
|
45
|
+
: 'Consents: none recorded');
|
|
46
|
+
}
|
|
47
|
+
return { success: true, data };
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
const disableSub = {
|
|
51
|
+
name: 'disable',
|
|
52
|
+
description: 'Disable all funnel surfaces (user-level, persists across projects)',
|
|
53
|
+
action: async () => {
|
|
54
|
+
setUserConfigEnabled(false);
|
|
55
|
+
recordDisclosureDeclined();
|
|
56
|
+
deleteFunnelData(); // opt-out deletes the pseudonymous ID + local event queue
|
|
57
|
+
output.printSuccess('Funnel disabled. All promotional surfaces are off; local funnel data deleted.');
|
|
58
|
+
return { success: true };
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
const enableSub = {
|
|
62
|
+
name: 'enable',
|
|
63
|
+
description: 'Re-enable funnel surfaces at the user tier (env/enterprise disables still win)',
|
|
64
|
+
action: async () => {
|
|
65
|
+
setUserConfigEnabled(true);
|
|
66
|
+
recordDisclosureReenabled();
|
|
67
|
+
const decision = resolveFunnelEnabled();
|
|
68
|
+
if (decision.enabled) {
|
|
69
|
+
output.printSuccess('Funnel enabled.');
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// A lower-precedence source never overrides a higher-precedence disable.
|
|
73
|
+
output.printWarning(`User preference recorded, but the funnel stays disabled by a higher-precedence source: ${decision.decidedBy}`);
|
|
74
|
+
}
|
|
75
|
+
return { success: true, data: decision };
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
const idSub = {
|
|
79
|
+
name: 'id',
|
|
80
|
+
description: 'Print the pseudonymous funnel ID (exists only with telemetry consent)',
|
|
81
|
+
action: async () => {
|
|
82
|
+
const id = getFunnelId();
|
|
83
|
+
if (id) {
|
|
84
|
+
output.writeln(id);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
output.writeln('No funnel ID (telemetry consent not granted, or funnel data deleted).');
|
|
88
|
+
}
|
|
89
|
+
return { success: true, data: { id } };
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
export const funnelCommand = {
|
|
93
|
+
name: 'funnel',
|
|
94
|
+
description: 'Control the Cognitum lifecycle funnel surfaces (tips, enrollment, notices)',
|
|
95
|
+
subcommands: [statusSub, disableSub, enableSub, idSub],
|
|
96
|
+
examples: [
|
|
97
|
+
{ command: 'ruflo funnel status', description: 'Effective state + deciding source' },
|
|
98
|
+
{ command: 'ruflo funnel disable', description: 'Turn off every funnel surface' },
|
|
99
|
+
],
|
|
100
|
+
action: statusSub.action,
|
|
101
|
+
};
|
|
102
|
+
export default funnelCommand;
|
|
103
|
+
//# sourceMappingURL=funnel.js.map
|
|
@@ -8,6 +8,7 @@ import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
|
8
8
|
import { storeCommand } from './transfer-store.js';
|
|
9
9
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
|
+
import { getSecurityStatus as sharedGetSecurityStatus, getSwarmStatus as sharedGetSwarmStatus, getGitUncommittedCount as sharedGetGitUncommittedCount, } from '../funnel/local-signals.js';
|
|
11
12
|
/**
|
|
12
13
|
* #1686 — `?? 0` only defaults null/undefined; NaN slips through and
|
|
13
14
|
* surfaces as `"NaN"` (or earlier crashed `.toFixed`) in the metrics
|
|
@@ -3412,52 +3413,11 @@ const statuslineCommand = {
|
|
|
3412
3413
|
const dddProgress = Math.min(100, Math.floor((domainsCompleted / totalDomains) * 100));
|
|
3413
3414
|
return { domainsCompleted, totalDomains, dddProgress, patternsLearned: learning.patterns, sessionsCompleted: learning.sessions };
|
|
3414
3415
|
}
|
|
3415
|
-
//
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
if (fs.existsSync(scanResultsPath)) {
|
|
3421
|
-
try {
|
|
3422
|
-
const scans = fs.readdirSync(scanResultsPath).filter((f) => f.endsWith('.json'));
|
|
3423
|
-
cvesFixed = Math.min(totalCves, scans.length);
|
|
3424
|
-
}
|
|
3425
|
-
catch {
|
|
3426
|
-
// Ignore
|
|
3427
|
-
}
|
|
3428
|
-
}
|
|
3429
|
-
const auditPath = path.join(process.cwd(), '.swarm', 'security');
|
|
3430
|
-
if (fs.existsSync(auditPath)) {
|
|
3431
|
-
try {
|
|
3432
|
-
const audits = fs.readdirSync(auditPath).filter((f) => f.includes('audit'));
|
|
3433
|
-
cvesFixed = Math.min(totalCves, Math.max(cvesFixed, audits.length));
|
|
3434
|
-
}
|
|
3435
|
-
catch {
|
|
3436
|
-
// Ignore
|
|
3437
|
-
}
|
|
3438
|
-
}
|
|
3439
|
-
const status = cvesFixed >= totalCves ? 'CLEAN' : cvesFixed > 0 ? 'IN_PROGRESS' : 'PENDING';
|
|
3440
|
-
return { status, cvesFixed, totalCves };
|
|
3441
|
-
}
|
|
3442
|
-
// Get swarm status
|
|
3443
|
-
function getSwarmStatus() {
|
|
3444
|
-
let activeAgents = 0;
|
|
3445
|
-
let coordinationActive = false;
|
|
3446
|
-
const maxAgents = 15;
|
|
3447
|
-
const isWindows = process.platform === 'win32';
|
|
3448
|
-
try {
|
|
3449
|
-
const psCmd = isWindows
|
|
3450
|
-
? 'tasklist /FI "IMAGENAME eq node.exe" /NH 2>NUL | find /c /v "" 2>NUL || echo 0'
|
|
3451
|
-
: 'ps aux 2>/dev/null | grep -c agentic-flow || echo "0"';
|
|
3452
|
-
const ps = execSync(psCmd, { encoding: 'utf-8', timeout: 3000 });
|
|
3453
|
-
activeAgents = Math.max(0, parseInt(ps.trim()) - 1);
|
|
3454
|
-
coordinationActive = activeAgents > 0;
|
|
3455
|
-
}
|
|
3456
|
-
catch {
|
|
3457
|
-
// ps/tasklist unavailable or timed out — report zero
|
|
3458
|
-
}
|
|
3459
|
-
return { activeAgents, maxAgents, coordinationActive };
|
|
3460
|
-
}
|
|
3416
|
+
// Security/swarm status — shared with the advisor-tip refresh (ADR-316)
|
|
3417
|
+
// via funnel/local-signals.ts, a single source of truth so the two call
|
|
3418
|
+
// sites can never silently drift on what these signals mean.
|
|
3419
|
+
const getSecurityStatus = sharedGetSecurityStatus;
|
|
3420
|
+
const getSwarmStatus = sharedGetSwarmStatus;
|
|
3461
3421
|
// Get system metrics
|
|
3462
3422
|
function getSystemMetrics() {
|
|
3463
3423
|
let memoryMB = 0;
|
|
@@ -3554,12 +3514,30 @@ const statuslineCommand = {
|
|
|
3554
3514
|
const swarm = getSwarmStatus();
|
|
3555
3515
|
const system = getSystemMetrics();
|
|
3556
3516
|
const user = getUserInfo();
|
|
3517
|
+
const getGitUncommittedCount = sharedGetGitUncommittedCount;
|
|
3518
|
+
// Funnel promo row (ADR-301). The statusline is spawned with piped stdio
|
|
3519
|
+
// by an interactive host, so interactivity is asserted here; all other
|
|
3520
|
+
// gates (RUFLO_FUNNEL, enterprise policy, config, CI, disclosure,
|
|
3521
|
+
// rotation ratio) are enforced inside getFunnelPromo. Never allowed to
|
|
3522
|
+
// break the statusline.
|
|
3523
|
+
let promo = null;
|
|
3524
|
+
try {
|
|
3525
|
+
const { getFunnelPromo } = await import('../funnel/index.js');
|
|
3526
|
+
promo = getFunnelPromo({
|
|
3527
|
+
interactive: true,
|
|
3528
|
+
localInsights: { security, swarm, gitUncommittedCount: getGitUncommittedCount() },
|
|
3529
|
+
});
|
|
3530
|
+
}
|
|
3531
|
+
catch {
|
|
3532
|
+
promo = null;
|
|
3533
|
+
}
|
|
3557
3534
|
const statusData = {
|
|
3558
3535
|
user,
|
|
3559
3536
|
v3Progress: progress,
|
|
3560
3537
|
security,
|
|
3561
3538
|
swarm,
|
|
3562
3539
|
system,
|
|
3540
|
+
promo,
|
|
3563
3541
|
timestamp: new Date().toISOString()
|
|
3564
3542
|
};
|
|
3565
3543
|
// JSON output
|
|
@@ -4418,6 +4396,90 @@ const notifyCommand = {
|
|
|
4418
4396
|
return { success: true, data: { timestamp, level, message } };
|
|
4419
4397
|
}
|
|
4420
4398
|
};
|
|
4399
|
+
// Refresh-funnel subcommand — the fix for "promo doesn't load right away".
|
|
4400
|
+
//
|
|
4401
|
+
// refreshRemoteMessages() (funnel/message-transport.ts) is fire-and-forget
|
|
4402
|
+
// by design so the STATUSLINE's own per-render invocation never blocks on
|
|
4403
|
+
// a network call. But the statusline is spawned as a short-lived subprocess
|
|
4404
|
+
// per render (execSync from statusline-generator.ts) — a fire-and-forget
|
|
4405
|
+
// promise kicked off there has no "later" to run in; the process exits
|
|
4406
|
+
// before the HTTPS fetch can complete, so the local message cache never
|
|
4407
|
+
// actually gets written and the promo row never appears (confirmed live:
|
|
4408
|
+
// two consecutive cold-cache statusline renders, 5s apart, both returned
|
|
4409
|
+
// promo:null and the cache file was never created).
|
|
4410
|
+
//
|
|
4411
|
+
// This command exists to be invoked from a LONGER-LIVED context — the
|
|
4412
|
+
// SessionStart hook (see hook-handler.cjs's 'session-restore' handler,
|
|
4413
|
+
// which spawns this detached so it isn't killed when the hook's own
|
|
4414
|
+
// process exits, and isn't awaited so it doesn't add to the hook's own
|
|
4415
|
+
// timeout budget). One real, properly-awaited refresh attempt here, once
|
|
4416
|
+
// per session, is what actually gives refreshRemoteMessages() a chance to
|
|
4417
|
+
// finish before anything needs the cache.
|
|
4418
|
+
const refreshFunnelCommand = {
|
|
4419
|
+
name: 'refresh-funnel',
|
|
4420
|
+
description: 'Best-effort background refresh of the funnel message cache (internal — see hook-handler.cjs session-restore)',
|
|
4421
|
+
options: [
|
|
4422
|
+
{ name: 'quiet', description: 'Suppress output (used when spawned detached from a hook)', type: 'boolean', default: false },
|
|
4423
|
+
],
|
|
4424
|
+
action: async (ctx) => {
|
|
4425
|
+
try {
|
|
4426
|
+
const { refreshRemoteMessages } = await import('../funnel/index.js');
|
|
4427
|
+
const result = await refreshRemoteMessages();
|
|
4428
|
+
if (!ctx.flags.quiet) {
|
|
4429
|
+
output.writeln(JSON.stringify(result));
|
|
4430
|
+
}
|
|
4431
|
+
return { success: true, data: result };
|
|
4432
|
+
}
|
|
4433
|
+
catch (error) {
|
|
4434
|
+
// Fail silent by design (matches message-transport.ts's own "fail
|
|
4435
|
+
// silent" discipline) — a broken refresh must never surface as a
|
|
4436
|
+
// hook error, only ever as "no promo this session."
|
|
4437
|
+
if (!ctx.flags.quiet) {
|
|
4438
|
+
output.printError(`refresh-funnel failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4439
|
+
}
|
|
4440
|
+
return { success: true, data: { refreshed: false, skipped: 'error' } };
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
};
|
|
4444
|
+
// Refresh-advisor subcommand — ADR-316's co-pilot tip. Mirrors
|
|
4445
|
+
// refreshFunnelCommand exactly: a properly-awaited CLI subcommand meant to
|
|
4446
|
+
// be spawned DETACHED from hook-handler.cjs's session-restore handler, so a
|
|
4447
|
+
// real (potentially multi-second, real-money) `claude -p` call gets a
|
|
4448
|
+
// chance to finish without ever blocking or being awaited by the hook's own
|
|
4449
|
+
// process. refreshAdvisorTipIfStale() itself is the safety net that makes
|
|
4450
|
+
// this cheap to call on every session-restore: it checks consent + a 24h
|
|
4451
|
+
// TTL BEFORE spending anything, so most invocations are a no-op file read.
|
|
4452
|
+
const refreshAdvisorCommand = {
|
|
4453
|
+
name: 'refresh-advisor',
|
|
4454
|
+
description: 'Best-effort background refresh of the co-pilot advisor tip (internal — see hook-handler.cjs session-restore; ADR-316)',
|
|
4455
|
+
options: [
|
|
4456
|
+
{ name: 'quiet', description: 'Suppress output (used when spawned detached from a hook)', type: 'boolean', default: false },
|
|
4457
|
+
],
|
|
4458
|
+
action: async (ctx) => {
|
|
4459
|
+
try {
|
|
4460
|
+
const { refreshAdvisorTipIfStale } = await import('../funnel/advisor-tip.js');
|
|
4461
|
+
const { getSecurityStatus, getSwarmStatus, getGitUncommittedCount } = await import('../funnel/local-signals.js');
|
|
4462
|
+
const result = await refreshAdvisorTipIfStale({
|
|
4463
|
+
security: getSecurityStatus(),
|
|
4464
|
+
swarm: getSwarmStatus(),
|
|
4465
|
+
gitUncommittedCount: getGitUncommittedCount(),
|
|
4466
|
+
});
|
|
4467
|
+
if (!ctx.flags.quiet) {
|
|
4468
|
+
output.writeln(JSON.stringify(result));
|
|
4469
|
+
}
|
|
4470
|
+
return { success: true, data: result };
|
|
4471
|
+
}
|
|
4472
|
+
catch (error) {
|
|
4473
|
+
// Fail silent by design (matches refresh-funnel's own discipline) — a
|
|
4474
|
+
// broken advisor refresh must never surface as a hook error, only
|
|
4475
|
+
// ever as "no tip this window."
|
|
4476
|
+
if (!ctx.flags.quiet) {
|
|
4477
|
+
output.printError(`refresh-advisor failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4478
|
+
}
|
|
4479
|
+
return { success: true, data: { refreshed: false, reason: 'error' } };
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
};
|
|
4421
4483
|
// Main hooks command
|
|
4422
4484
|
export const hooksCommand = {
|
|
4423
4485
|
name: 'hooks',
|
|
@@ -4461,6 +4523,10 @@ export const hooksCommand = {
|
|
|
4461
4523
|
// Agent Teams integration
|
|
4462
4524
|
teammateIdleCommand,
|
|
4463
4525
|
taskCompletedCommand,
|
|
4526
|
+
// Funnel background refresh — see refreshFunnelCommand's own doc comment
|
|
4527
|
+
refreshFunnelCommand,
|
|
4528
|
+
// Advisor co-pilot tip background refresh — ADR-316
|
|
4529
|
+
refreshAdvisorCommand,
|
|
4464
4530
|
],
|
|
4465
4531
|
options: [],
|
|
4466
4532
|
examples: [
|
|
@@ -31,6 +31,7 @@ const commandLoaders = {
|
|
|
31
31
|
'hive-mind': () => import('./hive-mind.js'),
|
|
32
32
|
process: () => import('./process.js'),
|
|
33
33
|
daemon: () => import('./daemon.js'),
|
|
34
|
+
version: () => import('./version.js'),
|
|
34
35
|
// V3 Advanced Commands (less frequently used - lazy load)
|
|
35
36
|
neural: () => import('./neural.js'),
|
|
36
37
|
security: () => import('./security.js'),
|
|
@@ -73,6 +74,15 @@ const commandLoaders = {
|
|
|
73
74
|
metaharness: () => import('./metaharness.js'),
|
|
74
75
|
// Eject (ADR-150 Phase 2) — lift ruflo project into a renamed standalone harness
|
|
75
76
|
eject: () => import('./eject.js'),
|
|
77
|
+
// Cognitum lifecycle funnel controls (ADR-301/305/309)
|
|
78
|
+
funnel: () => import('./funnel.js'),
|
|
79
|
+
// User-facing preferences wrapper (ADR-311 copy discipline — no "funnel" in
|
|
80
|
+
// the user surface). Forwards to the funnel primitives internally.
|
|
81
|
+
settings: () => import('./settings.js'),
|
|
82
|
+
// Meta LLM Proxy — sponsored downtime capacity (ADR-304/307/313)
|
|
83
|
+
proxy: () => import('./proxy.js'),
|
|
84
|
+
// Fable co-pilot advisor tip in the statusline insight ticker (ADR-316)
|
|
85
|
+
advisor: () => import('./advisor.js'),
|
|
76
86
|
};
|
|
77
87
|
// Cache for loaded commands
|
|
78
88
|
const loadedCommands = new Map();
|